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      @Deprecated
172      public List<File> getSourceFiles(Language... langs) {
173        return toFiles(mainFiles(getLanguageKeys(langs)));
174      }
175    
176      @Deprecated
177      public List<File> getJavaSourceFiles() {
178        return getSourceFiles(Java.INSTANCE);
179      }
180    
181      public boolean hasJavaSourceFiles() {
182        return !mainFiles(Java.KEY).isEmpty();
183      }
184    
185      @Deprecated
186      public List<File> getTestFiles(Language... langs) {
187        return toFiles(testFiles(getLanguageKeys(langs)));
188      }
189    
190      @Deprecated
191      public boolean hasTestFiles(Language lang) {
192        return !testFiles(lang.getKey()).isEmpty();
193      }
194    
195      private List<InputFile> getFiles(List<File> directories, List<File> initialFiles, boolean applyExclusionPatterns, String... langs) {
196        List<InputFile> result = Lists.newArrayList();
197        if (directories == null) {
198          return result;
199        }
200    
201        IOFileFilter suffixFilter = getFileSuffixFilter(langs);
202        WildcardPattern[] exclusionPatterns = getExclusionPatterns(applyExclusionPatterns);
203    
204        IOFileFilter initialFilesFilter = TrueFileFilter.INSTANCE;
205        if (initialFiles!=null && !initialFiles.isEmpty()) {
206          initialFilesFilter = new FileSelectionFilter(initialFiles);
207        }
208    
209        for (File dir : directories) {
210          if (dir.exists()) {
211            IOFileFilter exclusionFilter = new ExclusionFilter(dir, exclusionPatterns);
212            IOFileFilter visibleFileFilter = HiddenFileFilter.VISIBLE;
213            List<IOFileFilter> fileFilters = Lists.newArrayList(visibleFileFilter, suffixFilter, exclusionFilter, initialFilesFilter);
214            fileFilters.addAll(this.filters);
215            
216            List<File> files = (List<File>) FileUtils.listFiles(dir, new AndFileFilter(fileFilters), HiddenFileFilter.VISIBLE);
217            for (File file : files) {
218              String relativePath = DefaultProjectFileSystem.getRelativePath(file, dir);
219              result.add(InputFileUtils.create(dir, relativePath));
220            }
221          }
222        }
223        return result;
224      }
225    
226      private WildcardPattern[] getExclusionPatterns(boolean applyExclusionPatterns) {
227        WildcardPattern[] exclusionPatterns;
228        if (applyExclusionPatterns) {
229          exclusionPatterns = WildcardPattern.create(project.getExclusionPatterns());
230        } else {
231          exclusionPatterns = new WildcardPattern[0];
232        }
233        return exclusionPatterns;
234      }
235    
236      private IOFileFilter getFileSuffixFilter(String... langKeys) {
237        IOFileFilter suffixFilter = FileFilterUtils.trueFileFilter();
238        if (langKeys != null && langKeys.length > 0) {
239          List<String> suffixes = Arrays.asList(languages.getSuffixes(langKeys));
240          if (!suffixes.isEmpty()) {
241            suffixFilter = new SuffixFileFilter(suffixes);
242          }
243        }
244        return suffixFilter;
245      }
246    
247      private static class ExclusionFilter implements IOFileFilter {
248        File sourceDir;
249        WildcardPattern[] patterns;
250    
251        ExclusionFilter(File sourceDir, WildcardPattern[] patterns) {
252          this.sourceDir = sourceDir;
253          this.patterns = patterns;
254        }
255    
256        public boolean accept(File file) {
257          String relativePath = getRelativePath(file, sourceDir);
258          if (relativePath == null) {
259            return false;
260          }
261          for (WildcardPattern pattern : patterns) {
262            if (pattern.match(relativePath)) {
263              return false;
264            }
265          }
266          return true;
267        }
268    
269        public boolean accept(File file, String name) {
270          return accept(file);
271        }
272      }
273    
274      static class FileSelectionFilter implements IOFileFilter {
275        private Set<File> files;
276    
277        public FileSelectionFilter(List<File> f) {
278          files = Sets.newHashSet(f);
279        }
280    
281        public boolean accept(File file) {
282          return files.contains(file);
283        }
284    
285        public boolean accept(File file, String name) {
286          return accept(file);
287        }
288      }
289    
290      public File writeToWorkingDirectory(String content, String fileName) throws IOException {
291        return writeToFile(content, getSonarWorkingDirectory(), fileName);
292      }
293    
294      protected static File writeToFile(String content, File dir, String fileName) throws IOException {
295        File file = new File(dir, fileName);
296        FileUtils.writeStringToFile(file, content, CharEncoding.UTF_8);
297        return file;
298      }
299    
300      /**
301       * getRelativePath("c:/foo/src/my/package/Hello.java", "c:/foo/src") is "my/package/Hello.java"
302       * 
303       * @return null if file is not in dir (including recursive subdirectories)
304       */
305      public static String getRelativePath(File file, File dir) {
306        return getRelativePath(file, Arrays.asList(dir));
307      }
308    
309      /**
310       * getRelativePath("c:/foo/src/my/package/Hello.java", ["c:/bar", "c:/foo/src"]) is "my/package/Hello.java".
311       * <p>
312       * Relative path is composed of slashes. Windows backslaches are replaced by /
313       * </p>
314       * 
315       * @return null if file is not in dir (including recursive subdirectories)
316       */
317      public static String getRelativePath(File file, List<File> dirs) {
318        List<String> stack = new ArrayList<String>();
319        String path = FilenameUtils.normalize(file.getAbsolutePath());
320        File cursor = new File(path);
321        while (cursor != null) {
322          if (containsFile(dirs, cursor)) {
323            return StringUtils.join(stack, "/");
324          }
325          stack.add(0, cursor.getName());
326          cursor = cursor.getParentFile();
327        }
328        return null;
329      }
330    
331      public File getFileFromBuildDirectory(String filename) {
332        File file = new File(getBuildDir(), filename);
333        return (file.exists() ? file : null);
334      }
335    
336      public Resource toResource(File file) {
337        if (file == null || !file.exists()) {
338          return null;
339        }
340    
341        String relativePath = getRelativePath(file, getSourceDirs());
342        if (relativePath == null) {
343          return null;
344        }
345    
346        return (file.isFile() ? new org.sonar.api.resources.File(relativePath) : new org.sonar.api.resources.Directory(relativePath));
347      }
348    
349      private static boolean containsFile(List<File> dirs, File cursor) {
350        for (File dir : dirs) {
351          if (FilenameUtils.equalsNormalizedOnSystem(dir.getAbsolutePath(), cursor.getAbsolutePath())) {
352            return true;
353          }
354        }
355        return false;
356      }
357    
358      /**
359       * Conversion from Language to key. Allows to provide backward compatibility.
360       */
361      private String[] getLanguageKeys(Language[] langs) {
362        String[] keys = new String[langs.length];
363        for (int i = 0; i < langs.length; i++) {
364          keys[i] = langs[i].getKey();
365        }
366        return keys;
367      }
368    
369      /**
370       * Conversion from InputFile to File. Allows to provide backward compatibility.
371       */
372      private static List<File> toFiles(List<InputFile> files) {
373        List<File> result = Lists.newArrayList();
374        for (InputFile file : files) {
375          result.add(file.getFile());
376        }
377        return result;
378      }
379    
380      /**
381       * @since 2.6
382       */
383      public List<InputFile> mainFiles(String... langs) {
384        return getFiles(getSourceDirs(), getInitialSourceFiles(), true, langs);
385      }
386    
387      /**
388       * @since 2.6
389       */
390      public List<InputFile> testFiles(String... langs) {
391        return getFiles(getTestDirs(), getInitialTestFiles(), false /* FIXME should be true? */, langs);
392      }
393    
394      protected List<File> getInitialSourceFiles() {
395        return Collections.emptyList();
396      }
397    
398      protected List<File> getInitialTestFiles() {
399        return Collections.emptyList();
400      }
401    }