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