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