001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2009 SonarSource SA
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.collect.Lists;
023    import org.apache.commons.io.FileUtils;
024    import org.apache.commons.io.FilenameUtils;
025    import org.apache.commons.io.filefilter.*;
026    import org.apache.commons.lang.CharEncoding;
027    import org.apache.commons.lang.StringUtils;
028    import org.sonar.api.batch.FileFilter;
029    import org.sonar.api.batch.maven.MavenUtils;
030    import org.sonar.api.utils.SonarException;
031    import org.sonar.api.utils.WildcardPattern;
032    
033    import java.io.File;
034    import java.io.IOException;
035    import java.nio.charset.Charset;
036    import java.util.ArrayList;
037    import java.util.Arrays;
038    import java.util.List;
039    
040    /**
041     * An implementation  of ProjectFileSystem
042     *
043     * @since 1.10
044     */
045    public class DefaultProjectFileSystem implements ProjectFileSystem {
046    
047      private Project project;
048      private List<IOFileFilter> filters = Lists.newArrayList();
049    
050      /**
051       * Creates a DefaultProjectFileSystem based on a project
052       *
053       * @param project
054       */
055      public DefaultProjectFileSystem(Project project) {
056        this.project = project;
057      }
058    
059      /**
060       * Source encoding. Never null, it returns the default plateform charset if it is not defined in project.
061       */
062      public Charset getSourceCharset() {
063        return MavenUtils.getSourceCharset(project.getPom());
064      }
065    
066    
067      public DefaultProjectFileSystem addFileFilters(List<FileFilter> l) {
068        for (FileFilter fileFilter : l) {
069          addFileFilter(fileFilter);
070        }
071        return this;
072      }
073    
074      public DefaultProjectFileSystem addFileFilter(FileFilter fileFilter) {
075        filters.add(new DelegateFileFilter(fileFilter));
076        return this;
077      }
078    
079      /**
080       * Basedir is the project root directory.
081       */
082      public File getBasedir() {
083        return project.getPom().getBasedir();
084      }
085    
086      /**
087       * Build directory is by default "target" in maven projects.
088       */
089      public File getBuildDir() {
090        return resolvePath(project.getPom().getBuild().getDirectory());
091      }
092    
093      /**
094       * Directory where classes are placed. By default "target/classes" in maven projects.
095       */
096      public File getBuildOutputDir() {
097        return resolvePath(project.getPom().getBuild().getOutputDirectory());
098      }
099    
100      /**
101       * The list of directories for sources
102       */
103      public List<File> getSourceDirs() {
104        return resolvePaths(project.getPom().getCompileSourceRoots());
105      }
106    
107      /**
108       * Adds a source directory
109       *
110       * @return the current object
111       */
112      public DefaultProjectFileSystem addSourceDir(File dir) {
113        if (dir == null) {
114          throw new IllegalArgumentException("Can not add null to project source dirs");
115        }
116        project.getPom().getCompileSourceRoots().add(0, dir.getAbsolutePath());
117        return this;
118      }
119    
120      /**
121       * The list of directories for tests
122       */
123      public List<File> getTestDirs() {
124        return resolvePaths(project.getPom().getTestCompileSourceRoots());
125      }
126    
127      /**
128       * Adds a test directory
129       *
130       * @return the current object
131       */
132      public DefaultProjectFileSystem addTestDir(File dir) {
133        if (dir == null) {
134          throw new IllegalArgumentException("Can not add null to project test dirs");
135        }
136        project.getPom().getTestCompileSourceRoots().add(0, dir.getAbsolutePath());
137        return this;
138      }
139    
140      /**
141       * @return the directory where reporting is placed. Default is target/sites
142       */
143      public File getReportOutputDir() {
144        return resolvePath(project.getPom().getReporting().getOutputDirectory());
145      }
146    
147      /**
148       * @return the Sonar working directory. Default is "target/sonar"
149       */
150      public File getSonarWorkingDirectory() {
151        try {
152          File dir = new File(project.getPom().getBuild().getDirectory(), "sonar");
153          FileUtils.forceMkdir(dir);
154          return dir;
155    
156        } catch (IOException e) {
157          throw new SonarException("Unable to retrieve Sonar working directory.", e);
158        }
159      }
160    
161      public File resolvePath(String path) {
162        File file = new File(path);
163        if (!file.isAbsolute()) {
164          file = new File(project.getPom().getBasedir(), path);
165        }
166        return file;
167      }
168    
169      private List<File> resolvePaths(List<String> paths) {
170        List<File> result = new ArrayList<File>();
171        if (paths != null) {
172          for (String path : paths) {
173            result.add(resolvePath(path));
174          }
175        }
176    
177        return result;
178      }
179    
180      /**
181       * Gets the list of source files for given languages
182       *
183       * @param langs language filter. If null or empty, will return empty list
184       */
185      public List<File> getSourceFiles(Language... langs) {
186        return getFiles(getSourceDirs(), true, langs);
187      }
188    
189      /**
190       * Gets the list of java source files
191       */
192      public List<File> getJavaSourceFiles() {
193        return getSourceFiles(Java.INSTANCE);
194      }
195    
196      /**
197       * @return whether there are java source
198       */
199      public boolean hasJavaSourceFiles() {
200        return !getJavaSourceFiles().isEmpty();
201      }
202    
203      /**
204       * Gets the list of test files for given languages
205       *
206       * @param langs language filter. If null or empty, will return empty list
207       */
208      public List<File> getTestFiles(Language... langs) {
209        return getFiles(getTestDirs(), false, langs);
210      }
211    
212      /**
213       * @return whether there are tests files
214       */
215      public boolean hasTestFiles(Language lang) {
216        return !getTestFiles(lang).isEmpty();
217      }
218    
219      private List<File> getFiles(List<File> directories, boolean applyExclusionPatterns, Language... langs) {
220        List<File> result = new ArrayList<File>();
221        if (directories == null) {
222          return result;
223        }
224    
225        IOFileFilter suffixFilter = getFileSuffixFilter(langs);
226        WildcardPattern[] exclusionPatterns = getExclusionPatterns(applyExclusionPatterns);
227    
228        for (File dir : directories) {
229          if (dir.exists()) {
230            IOFileFilter exclusionFilter = new ExclusionFilter(dir, exclusionPatterns);
231            IOFileFilter visibleFileFilter = HiddenFileFilter.VISIBLE;
232            List dirFilters = Lists.newArrayList(visibleFileFilter, suffixFilter, exclusionFilter);
233            dirFilters.addAll(this.filters);
234            result.addAll(FileUtils.listFiles(dir, new AndFileFilter(dirFilters), HiddenFileFilter.VISIBLE));
235          }
236        }
237        return result;
238      }
239    
240      private WildcardPattern[] getExclusionPatterns(boolean applyExclusionPatterns) {
241        WildcardPattern[] exclusionPatterns;
242        if (applyExclusionPatterns) {
243          exclusionPatterns = WildcardPattern.create(project.getExclusionPatterns());
244        } else {
245          exclusionPatterns = new WildcardPattern[0];
246        }
247        return exclusionPatterns;
248      }
249    
250      private IOFileFilter getFileSuffixFilter(Language... langs) {
251        IOFileFilter suffixFilter = FileFilterUtils.trueFileFilter();
252        if (langs != null && langs.length > 0) {
253          List<String> suffixes = new ArrayList<String>();
254          for (Language lang : langs) {
255            if (lang.getFileSuffixes() != null) {
256              suffixes.addAll(Arrays.asList(lang.getFileSuffixes()));
257            }
258          }
259          if (!suffixes.isEmpty()) {
260            suffixFilter = new SuffixFileFilter(suffixes);
261          }
262        }
263    
264        return suffixFilter;
265      }
266    
267      private static class ExclusionFilter implements IOFileFilter {
268        File sourceDir;
269        WildcardPattern[] patterns;
270    
271        ExclusionFilter(File sourceDir, WildcardPattern[] patterns) {
272          this.sourceDir = sourceDir;
273          this.patterns = patterns;
274        }
275    
276        public boolean accept(File file) {
277          String relativePath = getRelativePath(file, sourceDir);
278          if (relativePath == null) {
279            return false;
280          }
281          for (WildcardPattern pattern : patterns) {
282            if (pattern.match(relativePath)) {
283              return false;
284            }
285          }
286          return true;
287        }
288    
289        public boolean accept(File file, String name) {
290          return accept(file);
291        }
292      }
293    
294      /**
295       * Save data into a new file of Sonar working directory.
296       *
297       * @return the created file
298       */
299      public File writeToWorkingDirectory(String content, String fileName) throws IOException {
300        return writeToFile(content, getSonarWorkingDirectory(), fileName);
301      }
302    
303      protected static File writeToFile(String content, File dir, String fileName) throws IOException {
304        File file = new File(dir, fileName);
305        FileUtils.writeStringToFile(file, content, CharEncoding.UTF_8);
306        return file;
307      }
308    
309      /**
310       * getRelativePath("c:/foo/src/my/package/Hello.java", "c:/foo/src") is "my/package/Hello.java"
311       *
312       * @return null if file is not in dir (including recursive subdirectories)
313       */
314      public static String getRelativePath(File file, File dir) {
315        return getRelativePath(file, Arrays.asList(dir));
316      }
317    
318      /**
319       * getRelativePath("c:/foo/src/my/package/Hello.java", ["c:/bar", "c:/foo/src"]) is "my/package/Hello.java".
320       * <p/>
321       * <p>Relative path is composed of slashes. Windows backslaches are replaced by /</p>
322       *
323       * @return null if file is not in dir (including recursive subdirectories)
324       */
325      public static String getRelativePath(File file, List<File> dirs) {
326        List<String> stack = new ArrayList<String>();
327        String path = FilenameUtils.normalize(file.getAbsolutePath());
328        File cursor = new File(path);
329        while (cursor != null) {
330          if (containsFile(dirs, cursor)) {
331            return StringUtils.join(stack, "/");
332          }
333          stack.add(0, cursor.getName());
334          cursor = cursor.getParentFile();
335        }
336        return null;
337      }
338    
339      public File getFileFromBuildDirectory(String filename) {
340        File file = new File(getBuildDir(), filename);
341        return (file.exists() ? file : null);
342      }
343    
344      public Resource toResource(File file) {
345        if (file == null || !file.exists()) {
346          return null;
347        }
348    
349        String relativePath = getRelativePath(file, getSourceDirs());
350        if (relativePath == null) {
351          return null;
352        }
353    
354        return (file.isFile() ? new org.sonar.api.resources.File(relativePath) : new org.sonar.api.resources.Directory(relativePath));
355      }
356    
357      private static boolean containsFile(List<File> dirs, File cursor) {
358        for (File dir : dirs) {
359          if (FilenameUtils.equalsNormalizedOnSystem(dir.getAbsolutePath(), cursor.getAbsolutePath())) {
360            return true;
361          }
362        }
363        return false;
364      }
365    }