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.batch.scan.filesystem;
021    
022    import com.google.common.base.Function;
023    import com.google.common.collect.Collections2;
024    import com.google.common.collect.ImmutableList;
025    import com.google.common.collect.Lists;
026    import org.apache.commons.lang.StringUtils;
027    import org.slf4j.Logger;
028    import org.slf4j.LoggerFactory;
029    import org.sonar.api.CoreProperties;
030    import org.sonar.api.batch.bootstrap.ProjectDefinition;
031    import org.sonar.api.batch.fs.FilePredicate;
032    import org.sonar.api.batch.fs.internal.DefaultFileSystem;
033    import org.sonar.api.config.Settings;
034    import org.sonar.api.resources.Project;
035    import org.sonar.api.scan.filesystem.FileQuery;
036    import org.sonar.api.scan.filesystem.ModuleFileSystem;
037    import org.sonar.api.utils.MessageException;
038    
039    import javax.annotation.CheckForNull;
040    import javax.annotation.Nullable;
041    
042    import java.io.File;
043    import java.nio.charset.Charset;
044    import java.util.ArrayList;
045    import java.util.Collection;
046    import java.util.List;
047    import java.util.Map;
048    
049    /**
050     * This class can't be immutable because of execution of maven plugins that can change the project structure (see MavenPluginHandler and sonar.phase)
051     *
052     * @since 3.5
053     */
054    public class DefaultModuleFileSystem extends DefaultFileSystem implements ModuleFileSystem {
055    
056      private static final Logger LOG = LoggerFactory.getLogger(DefaultModuleFileSystem.class);
057    
058      private final String moduleKey;
059      private final FileIndexer indexer;
060      private final Settings settings;
061    
062      private File buildDir;
063      private List<File> sourceDirsOrFiles = Lists.newArrayList();
064      private List<File> testDirsOrFiles = Lists.newArrayList();
065      private List<File> binaryDirs = Lists.newArrayList();
066      private ComponentIndexer componentIndexer;
067      private boolean initialized;
068    
069      /**
070       * Used by scan2 
071       */
072      public DefaultModuleFileSystem(ModuleInputFileCache moduleInputFileCache, ProjectDefinition def, Settings settings,
073        FileIndexer indexer, ModuleFileSystemInitializer initializer) {
074        this(moduleInputFileCache, def.getKey(), settings, indexer, initializer, null);
075      }
076    
077      public DefaultModuleFileSystem(ModuleInputFileCache moduleInputFileCache, ProjectDefinition def, Project project,
078        Settings settings, FileIndexer indexer,
079        ModuleFileSystemInitializer initializer,
080        ComponentIndexer componentIndexer) {
081        this(moduleInputFileCache, project.getKey(), settings, indexer, initializer, componentIndexer);
082      }
083    
084      private DefaultModuleFileSystem(ModuleInputFileCache moduleInputFileCache, String moduleKey, Settings settings,
085        FileIndexer indexer, ModuleFileSystemInitializer initializer,
086        @Nullable ComponentIndexer componentIndexer) {
087        super(initializer.baseDir(), moduleInputFileCache);
088        this.componentIndexer = componentIndexer;
089        this.moduleKey = moduleKey;
090        this.settings = settings;
091        this.indexer = indexer;
092        setWorkDir(initializer.workingDir());
093        this.buildDir = initializer.buildDir();
094        this.sourceDirsOrFiles = initializer.sources();
095        this.testDirsOrFiles = initializer.tests();
096        this.binaryDirs = initializer.binaryDirs();
097      }
098    
099      public boolean isInitialized() {
100        return initialized;
101      }
102    
103      public String moduleKey() {
104        return moduleKey;
105      }
106    
107      @Override
108      @CheckForNull
109      public File buildDir() {
110        return buildDir;
111      }
112    
113      @Override
114      public List<File> sourceDirs() {
115        return keepOnlyDirs(sourceDirsOrFiles);
116      }
117    
118      public List<File> sources() {
119        return sourceDirsOrFiles;
120      }
121    
122      @Override
123      public List<File> testDirs() {
124        return keepOnlyDirs(testDirsOrFiles);
125      }
126    
127      public List<File> tests() {
128        return testDirsOrFiles;
129      }
130    
131      private List<File> keepOnlyDirs(List<File> dirsOrFiles) {
132        List<File> result = new ArrayList<File>();
133        for (File f : dirsOrFiles) {
134          if (f.isDirectory()) {
135            result.add(f);
136          }
137        }
138        return result;
139      }
140    
141      @Override
142      public List<File> binaryDirs() {
143        return binaryDirs;
144      }
145    
146      @Override
147      public Charset encoding() {
148        final Charset charset;
149        String encoding = settings.getString(CoreProperties.ENCODING_PROPERTY);
150        if (StringUtils.isNotEmpty(encoding)) {
151          charset = Charset.forName(StringUtils.trim(encoding));
152        } else {
153          charset = Charset.defaultCharset();
154        }
155        return charset;
156      }
157    
158      @Override
159      public boolean isDefaultJvmEncoding() {
160        return !settings.hasKey(CoreProperties.ENCODING_PROPERTY);
161      }
162    
163      /**
164       * Should not be used - only for old plugins
165       *
166       * @deprecated since 4.0
167       */
168      @Deprecated
169      void addSourceDir(File dir) {
170        throw modificationNotPermitted();
171      }
172    
173      /**
174       * Should not be used - only for old plugins
175       *
176       * @deprecated since 4.0
177       */
178      @Deprecated
179      void addTestDir(File dir) {
180        throw modificationNotPermitted();
181      }
182    
183      private UnsupportedOperationException modificationNotPermitted() {
184        return new UnsupportedOperationException("Modifications of the file system are not permitted");
185      }
186    
187      /**
188       * @return
189       * @deprecated in 4.2. Replaced by {@link #encoding()}
190       */
191      @Override
192      @Deprecated
193      public Charset sourceCharset() {
194        return encoding();
195      }
196    
197      /**
198       * @deprecated in 4.2. Replaced by {@link #workDir()}
199       */
200      @Deprecated
201      @Override
202      public File workingDir() {
203        return workDir();
204      }
205    
206      @Override
207      public List<File> files(FileQuery query) {
208        doPreloadFiles();
209        Collection<FilePredicate> predicates = Lists.newArrayList();
210        for (Map.Entry<String, Collection<String>> entry : query.attributes().entrySet()) {
211          predicates.add(fromDeprecatedAttribute(entry.getKey(), entry.getValue()));
212        }
213        return ImmutableList.copyOf(files(predicates().and(predicates)));
214      }
215    
216      @Override
217      protected void doPreloadFiles() {
218        if (!initialized) {
219          LOG.warn("Accessing the filesystem before the Sensor phase is deprecated and will not be supported in the future. Please update your plugin.");
220          indexer.index(this);
221        }
222      }
223    
224      public void resetDirs(File basedir, File buildDir, List<File> sourceDirs, List<File> testDirs, List<File> binaryDirs) {
225        if (initialized) {
226          throw MessageException.of("Module filesystem is already initialized. Modifications of filesystem are only allowed during Initializer phase.");
227        }
228        setBaseDir(basedir);
229        this.buildDir = buildDir;
230        this.sourceDirsOrFiles = existingDirsOrFiles(sourceDirs);
231        this.testDirsOrFiles = existingDirsOrFiles(testDirs);
232        this.binaryDirs = existingDirsOrFiles(binaryDirs);
233      }
234    
235      public void index() {
236        if (initialized) {
237          throw MessageException.of("Module filesystem can only be indexed once");
238        }
239        initialized = true;
240        indexer.index(this);
241        if (componentIndexer != null) {
242          componentIndexer.execute(this);
243        }
244      }
245    
246      private List<File> existingDirsOrFiles(List<File> dirsOrFiles) {
247        ImmutableList.Builder<File> builder = ImmutableList.builder();
248        for (File dirOrFile : dirsOrFiles) {
249          if (dirOrFile.exists()) {
250            builder.add(dirOrFile);
251          }
252        }
253        return builder.build();
254      }
255    
256      private FilePredicate fromDeprecatedAttribute(String key, Collection<String> value) {
257        if ("TYPE".equals(key)) {
258          return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() {
259            @Override
260            public FilePredicate apply(@Nullable String s) {
261              return s == null ? predicates().all() : predicates().hasType(org.sonar.api.batch.fs.InputFile.Type.valueOf(s));
262            }
263          }));
264        }
265        if ("STATUS".equals(key)) {
266          return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() {
267            @Override
268            public FilePredicate apply(@Nullable String s) {
269              return s == null ? predicates().all() : predicates().hasStatus(org.sonar.api.batch.fs.InputFile.Status.valueOf(s));
270            }
271          }));
272        }
273        if ("LANG".equals(key)) {
274          return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() {
275            @Override
276            public FilePredicate apply(@Nullable String s) {
277              return s == null ? predicates().all() : predicates().hasLanguage(s);
278            }
279          }));
280        }
281        if ("CMP_KEY".equals(key)) {
282          return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() {
283            @Override
284            public FilePredicate apply(@Nullable String s) {
285              return s == null ? predicates().all() : new AdditionalFilePredicates.KeyPredicate(s);
286            }
287          }));
288        }
289        if ("CMP_DEPRECATED_KEY".equals(key)) {
290          return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() {
291            @Override
292            public FilePredicate apply(@Nullable String s) {
293              return s == null ? predicates().all() : new AdditionalFilePredicates.DeprecatedKeyPredicate(s);
294            }
295          }));
296        }
297        if ("SRC_REL_PATH".equals(key)) {
298          return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() {
299            @Override
300            public FilePredicate apply(@Nullable String s) {
301              return s == null ? predicates().all() : new AdditionalFilePredicates.SourceRelativePathPredicate(s);
302            }
303          }));
304        }
305        if ("SRC_DIR_PATH".equals(key)) {
306          return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() {
307            @Override
308            public FilePredicate apply(@Nullable String s) {
309              return s == null ? predicates().all() : new AdditionalFilePredicates.SourceDirPredicate(s);
310            }
311          }));
312        }
313        throw new IllegalArgumentException("Unsupported file attribute: " + key);
314      }
315    
316      @Override
317      public boolean equals(Object o) {
318        if (this == o) {
319          return true;
320        }
321        if (o == null || getClass() != o.getClass()) {
322          return false;
323        }
324        DefaultModuleFileSystem that = (DefaultModuleFileSystem) o;
325        return moduleKey.equals(that.moduleKey);
326      }
327    
328      @Override
329      public int hashCode() {
330        return moduleKey.hashCode();
331      }
332    }