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