001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2012 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar 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     * Sonar 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
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.api.resources;
021    
022    import com.google.common.base.Predicate;
023    import com.google.common.collect.ImmutableList;
024    import com.google.common.collect.Iterables;
025    import com.google.common.collect.Lists;
026    import com.google.common.collect.Sets;
027    import org.apache.commons.io.FileUtils;
028    import org.apache.commons.io.FilenameUtils;
029    import org.apache.commons.io.filefilter.*;
030    import org.apache.commons.lang.CharEncoding;
031    import org.apache.commons.lang.StringUtils;
032    import org.sonar.api.CoreProperties;
033    import org.sonar.api.batch.FileFilter;
034    import org.sonar.api.utils.Logs;
035    import org.sonar.api.utils.SonarException;
036    import org.sonar.api.utils.WildcardPattern;
037    
038    import javax.annotation.Nullable;
039    import java.io.File;
040    import java.io.IOException;
041    import java.nio.charset.Charset;
042    import java.util.*;
043    
044    /**
045     * An implementation of {@link ProjectFileSystem}.
046     * For internal use only.
047     *
048     * @since 1.10
049     * @deprecated in 2.8. In fact this class should not be located in sonar-plugin-api and most of the methods were overridden by a component in sonar-batch.
050     */
051    @Deprecated
052    public class DefaultProjectFileSystem implements ProjectFileSystem {
053    
054      protected static final Predicate<File> DIRECTORY_EXISTS = new Predicate<File>() {
055        public boolean apply(@Nullable File input) {
056          return input != null && input.exists() && input.isDirectory();
057        }
058      };
059    
060      private Project project;
061      private Languages languages;
062      private List<IOFileFilter> filters = Lists.newArrayList();
063    
064      public DefaultProjectFileSystem(Project project, Languages languages) {
065        this.project = project;
066        this.languages = languages;
067      }
068    
069      public DefaultProjectFileSystem(Project project, Languages languages, FileFilter... fileFilters) {
070        this(project, languages);
071        for (FileFilter fileFilter : fileFilters) {
072          filters.add(new DelegateFileFilter(fileFilter));
073        }
074      }
075    
076      public Charset getSourceCharset() {
077        String encoding = project.getConfiguration().getString(CoreProperties.ENCODING_PROPERTY);
078        if (StringUtils.isNotEmpty(encoding)) {
079          try {
080            return Charset.forName(encoding);
081          } catch (Exception e) {
082            Logs.INFO.warn("Can not get project charset", e);
083          }
084        }
085        return Charset.defaultCharset();
086      }
087    
088      public File getBasedir() {
089        return project.getPom().getBasedir();
090      }
091    
092      public File getBuildDir() {
093        return resolvePath(project.getPom().getBuild().getDirectory());
094      }
095    
096      public File getBuildOutputDir() {
097        return resolvePath(project.getPom().getBuild().getOutputDirectory());
098      }
099    
100      /**
101       * Maven can modify source directories during Sonar execution - see MavenPhaseExecutor.
102       */
103      public List<File> getSourceDirs() {
104        return ImmutableList.copyOf(Iterables.filter(resolvePaths(project.getPom().getCompileSourceRoots()), DIRECTORY_EXISTS));
105      }
106    
107      /**
108       * @deprecated since 2.6, because should be immutable
109       */
110      @Deprecated
111      public DefaultProjectFileSystem addSourceDir(File dir) {
112        if (dir == null) {
113          throw new IllegalArgumentException("Can not add null to project source dirs");
114        }
115        project.getPom().getCompileSourceRoots().add(0, dir.getAbsolutePath());
116        return this;
117      }
118    
119      /**
120       * Maven can modify test directories during Sonar execution - see MavenPhaseExecutor.
121       */
122      public List<File> getTestDirs() {
123        return ImmutableList.copyOf(Iterables.filter(resolvePaths(project.getPom().getTestCompileSourceRoots()), DIRECTORY_EXISTS));
124      }
125    
126      /**
127       * @deprecated since 2.6, because should be immutable
128       */
129      @Deprecated
130      public DefaultProjectFileSystem addTestDir(File dir) {
131        if (dir == null) {
132          throw new IllegalArgumentException("Can not add null to project test dirs");
133        }
134        project.getPom().getTestCompileSourceRoots().add(0, dir.getAbsolutePath());
135        return this;
136      }
137    
138      public File getReportOutputDir() {
139        return resolvePath(project.getPom().getReporting().getOutputDirectory());
140      }
141    
142      public File getSonarWorkingDirectory() {
143        try {
144          File dir = new File(getBuildDir(), "sonar");
145          FileUtils.forceMkdir(dir);
146          return dir;
147    
148        } catch (IOException e) {
149          throw new SonarException("Unable to retrieve Sonar working directory.", e);
150        }
151      }
152    
153      public File resolvePath(String path) {
154        File file = new File(path);
155        if (!file.isAbsolute()) {
156          try {
157            file = new File(getBasedir(), path).getCanonicalFile();
158          } catch (IOException e) {
159            throw new SonarException("Unable to resolve path '" + path + "'", e);
160          }
161        }
162        return file;
163      }
164    
165      protected List<File> resolvePaths(List<String> paths) {
166        List<File> result = Lists.newArrayList();
167        if (paths != null) {
168          for (String path : paths) {
169            result.add(resolvePath(path));
170          }
171        }
172        return result;
173      }
174    
175      /**
176       * @deprecated in 2.6, use {@link #mainFiles(String...)} instead
177       */
178      @Deprecated
179      public List<File> getSourceFiles(Language... langs) {
180        return toFiles(mainFiles(getLanguageKeys(langs)));
181      }
182    
183      /**
184       * @deprecated in 2.6, use {@link #mainFiles(String...)} instead
185       */
186      @Deprecated
187      public List<File> getJavaSourceFiles() {
188        return getSourceFiles(Java.INSTANCE);
189      }
190    
191      public boolean hasJavaSourceFiles() {
192        return !mainFiles(Java.KEY).isEmpty();
193      }
194    
195      /**
196       * @deprecated in 2.6, use {@link #testFiles(String...)} instead
197       */
198      @Deprecated
199      public List<File> getTestFiles(Language... langs) {
200        return toFiles(testFiles(getLanguageKeys(langs)));
201      }
202    
203      /**
204       * @deprecated in 2.6
205       */
206      @Deprecated
207      public boolean hasTestFiles(Language lang) {
208        return !testFiles(lang.getKey()).isEmpty();
209      }
210    
211      List<InputFile> getFiles(List<File> directories, List<File> initialFiles, boolean applyExclusionPatterns, String... langs) {
212        List<InputFile> result = Lists.newArrayList();
213        if (directories == null) {
214          return result;
215        }
216    
217        IOFileFilter suffixFilter = getFileSuffixFilter(langs);
218        WildcardPattern[] exclusionPatterns = getExclusionPatterns(applyExclusionPatterns);
219    
220        IOFileFilter initialFilesFilter = TrueFileFilter.INSTANCE;
221        if (initialFiles != null && !initialFiles.isEmpty()) {
222          initialFilesFilter = new FileSelectionFilter(initialFiles);
223        }
224    
225        for (File dir : directories) {
226          if (dir.exists()) {
227            IOFileFilter exclusionFilter = new ExclusionFilter(dir, exclusionPatterns);
228            IOFileFilter visibleFileFilter = HiddenFileFilter.VISIBLE;
229            List<IOFileFilter> fileFilters = Lists.newArrayList(visibleFileFilter, suffixFilter, exclusionFilter, initialFilesFilter);
230            fileFilters.addAll(this.filters);
231    
232            IOFileFilter dotPrefixDirFilter = FileFilterUtils.notFileFilter(FileFilterUtils.prefixFileFilter("."));
233            List<File> files = (List<File>) FileUtils.listFiles(dir, new AndFileFilter(fileFilters), FileFilterUtils.and(HiddenFileFilter.VISIBLE, dotPrefixDirFilter));
234            for (File file : files) {
235              String relativePath = DefaultProjectFileSystem.getRelativePath(file, dir);
236              result.add(InputFileUtils.create(dir, relativePath));
237            }
238          }
239        }
240        return result;
241      }
242    
243      private WildcardPattern[] getExclusionPatterns(boolean applyExclusionPatterns) {
244        WildcardPattern[] exclusionPatterns;
245        if (applyExclusionPatterns) {
246          exclusionPatterns = WildcardPattern.create(project.getExclusionPatterns());
247        } else {
248          exclusionPatterns = new WildcardPattern[0];
249        }
250        return exclusionPatterns;
251      }
252    
253      private IOFileFilter getFileSuffixFilter(String... langKeys) {
254        IOFileFilter suffixFilter = FileFilterUtils.trueFileFilter();
255        if (langKeys != null && langKeys.length > 0) {
256          List<String> suffixes = Arrays.asList(languages.getSuffixes(langKeys));
257          if (!suffixes.isEmpty()) {
258            suffixFilter = new SuffixFileFilter(suffixes);
259          }
260        }
261        return suffixFilter;
262      }
263    
264      private static class ExclusionFilter implements IOFileFilter {
265        File sourceDir;
266        WildcardPattern[] patterns;
267    
268        ExclusionFilter(File sourceDir, WildcardPattern[] patterns) {
269          this.sourceDir = sourceDir;
270          this.patterns = patterns;
271        }
272    
273        public boolean accept(File file) {
274          String relativePath = getRelativePath(file, sourceDir);
275          if (relativePath == null) {
276            return false;
277          }
278          for (WildcardPattern pattern : patterns) {
279            if (pattern.match(relativePath)) {
280              return false;
281            }
282          }
283          return true;
284        }
285    
286        public boolean accept(File file, String name) {
287          return accept(file);
288        }
289      }
290    
291      static class FileSelectionFilter implements IOFileFilter {
292        private Set<File> files;
293    
294        public FileSelectionFilter(List<File> f) {
295          files = Sets.newHashSet(f);
296        }
297    
298        public boolean accept(File file) {
299          return files.contains(file);
300        }
301    
302        public boolean accept(File file, String name) {
303          return accept(file);
304        }
305      }
306    
307      public File writeToWorkingDirectory(String content, String fileName) throws IOException {
308        return writeToFile(content, getSonarWorkingDirectory(), fileName);
309      }
310    
311      protected static File writeToFile(String content, File dir, String fileName) throws IOException {
312        File file = new File(dir, fileName);
313        FileUtils.writeStringToFile(file, content, CharEncoding.UTF_8);
314        return file;
315      }
316    
317      /**
318       * getRelativePath("c:/foo/src/my/package/Hello.java", "c:/foo/src") is "my/package/Hello.java"
319       *
320       * @return null if file is not in dir (including recursive subdirectories)
321       */
322      public static String getRelativePath(File file, File dir) {
323        return getRelativePath(file, Arrays.asList(dir));
324      }
325    
326      /**
327       * getRelativePath("c:/foo/src/my/package/Hello.java", ["c:/bar", "c:/foo/src"]) is "my/package/Hello.java".
328       * <p>
329       * Relative path is composed of slashes. Windows backslaches are replaced by /
330       * </p>
331       *
332       * @return null if file is not in dir (including recursive subdirectories)
333       */
334      public static String getRelativePath(File file, List<File> dirs) {
335        List<String> stack = new ArrayList<String>();
336        String path = FilenameUtils.normalize(file.getAbsolutePath());
337        File cursor = new File(path);
338        while (cursor != null) {
339          if (containsFile(dirs, cursor)) {
340            return StringUtils.join(stack, "/");
341          }
342          stack.add(0, cursor.getName());
343          cursor = cursor.getParentFile();
344        }
345        return null;
346      }
347    
348      public File getFileFromBuildDirectory(String filename) {
349        File file = new File(getBuildDir(), filename);
350        return (file.exists() ? file : null);
351      }
352    
353      public Resource toResource(File file) {
354        if (file == null || !file.exists()) {
355          return null;
356        }
357    
358        String relativePath = getRelativePath(file, getSourceDirs());
359        if (relativePath == null) {
360          return null;
361        }
362    
363        return (file.isFile() ? new org.sonar.api.resources.File(relativePath) : new org.sonar.api.resources.Directory(relativePath));
364      }
365    
366      private static boolean containsFile(List<File> dirs, File cursor) {
367        for (File dir : dirs) {
368          if (FilenameUtils.equalsNormalizedOnSystem(dir.getAbsolutePath(), cursor.getAbsolutePath())) {
369            return true;
370          }
371        }
372        return false;
373      }
374    
375      /**
376       * Conversion from Language to key. Allows to provide backward compatibility.
377       */
378      private String[] getLanguageKeys(Language[] langs) {
379        String[] keys = new String[langs.length];
380        for (int i = 0; i < langs.length; i++) {
381          keys[i] = langs[i].getKey();
382        }
383        return keys;
384      }
385    
386      /**
387       * Conversion from InputFile to File. Allows to provide backward compatibility.
388       */
389      private static List<File> toFiles(List<InputFile> files) {
390        List<File> result = Lists.newArrayList();
391        for (InputFile file : files) {
392          result.add(file.getFile());
393        }
394        return result;
395      }
396    
397      /**
398       * @since 2.6
399       */
400      public List<InputFile> mainFiles(String... langs) {
401        return getFiles(getSourceDirs(), getInitialSourceFiles(), true, langs);
402      }
403    
404      /**
405       * @since 2.6
406       */
407      public List<InputFile> testFiles(String... langs) {
408        return getFiles(getTestDirs(), getInitialTestFiles(), false /* FIXME should be true? */, langs);
409      }
410    
411      protected List<File> getInitialSourceFiles() {
412        return Collections.emptyList();
413      }
414    
415      protected List<File> getInitialTestFiles() {
416        return Collections.emptyList();
417      }
418    }