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