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