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