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 }