001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2011 SonarSource
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.CoreProperties;
029 import org.sonar.api.batch.FileFilter;
030 import org.sonar.api.utils.Logs;
031 import org.sonar.api.utils.SonarException;
032 import org.sonar.api.utils.WildcardPattern;
033
034 import java.io.File;
035 import java.io.IOException;
036 import java.nio.charset.Charset;
037 import java.util.ArrayList;
038 import java.util.Arrays;
039 import java.util.List;
040
041 /**
042 * An implementation of {@link ProjectFileSystem}.
043 * For internal use only.
044 *
045 * @since 1.10
046 * @TODO in fact this class should not be located in sonar-plugin-api
047 */
048 public class DefaultProjectFileSystem implements ProjectFileSystem {
049
050 private Project project;
051 private Languages languages;
052 private List<IOFileFilter> filters = Lists.newArrayList();
053
054 public DefaultProjectFileSystem(Project project, Languages languages) {
055 this.project = project;
056 this.languages = languages;
057 }
058
059 public DefaultProjectFileSystem(Project project, Languages languages, FileFilter... fileFilters) {
060 this(project, languages);
061 for (FileFilter fileFilter : fileFilters) {
062 filters.add(new DelegateFileFilter(fileFilter));
063 }
064 }
065
066 public Charset getSourceCharset() {
067 String encoding = project.getConfiguration().getString(CoreProperties.ENCODING_PROPERTY);
068 if (StringUtils.isNotEmpty(encoding)) {
069 try {
070 return Charset.forName(encoding);
071 } catch (Exception e) {
072 Logs.INFO.warn("Can not get project charset", e);
073 }
074 }
075 return Charset.defaultCharset();
076 }
077
078 public File getBasedir() {
079 return project.getPom().getBasedir();
080 }
081
082 public File getBuildDir() {
083 return resolvePath(project.getPom().getBuild().getDirectory());
084 }
085
086 public File getBuildOutputDir() {
087 return resolvePath(project.getPom().getBuild().getOutputDirectory());
088 }
089
090 /**
091 * Maven can modify source directories during Sonar execution - see MavenPhaseExecutor.
092 */
093 public List<File> getSourceDirs() {
094 return resolvePaths(project.getPom().getCompileSourceRoots());
095 }
096
097 /**
098 * @deprecated since 2.6, because should be immutable
099 */
100 @Deprecated
101 public DefaultProjectFileSystem addSourceDir(File dir) {
102 if (dir == null) {
103 throw new IllegalArgumentException("Can not add null to project source dirs");
104 }
105 project.getPom().getCompileSourceRoots().add(0, dir.getAbsolutePath());
106 return this;
107 }
108
109 /**
110 * Maven can modify test directories during Sonar execution - see MavenPhaseExecutor.
111 */
112 public List<File> getTestDirs() {
113 return resolvePaths(project.getPom().getTestCompileSourceRoots());
114 }
115
116 /**
117 * @deprecated since 2.6, because should be immutable
118 */
119 @Deprecated
120 public DefaultProjectFileSystem addTestDir(File dir) {
121 if (dir == null) {
122 throw new IllegalArgumentException("Can not add null to project test dirs");
123 }
124 project.getPom().getTestCompileSourceRoots().add(0, dir.getAbsolutePath());
125 return this;
126 }
127
128 public File getReportOutputDir() {
129 return resolvePath(project.getPom().getReporting().getOutputDirectory());
130 }
131
132 public File getSonarWorkingDirectory() {
133 try {
134 File dir = new File(getBuildDir(), "sonar");
135 FileUtils.forceMkdir(dir);
136 return dir;
137
138 } catch (IOException e) {
139 throw new SonarException("Unable to retrieve Sonar working directory.", e);
140 }
141 }
142
143 public File resolvePath(String path) {
144 File file = new File(path);
145 if (!file.isAbsolute()) {
146 try {
147 file = new File(getBasedir(), path).getCanonicalFile();
148 } catch (IOException e) {
149 throw new SonarException("Unable to resolve path '" + path + "'", e);
150 }
151 }
152 return file;
153 }
154
155 private List<File> resolvePaths(List<String> paths) {
156 List<File> result = Lists.newArrayList();
157 if (paths != null) {
158 for (String path : paths) {
159 result.add(resolvePath(path));
160 }
161 }
162 return result;
163 }
164
165 @Deprecated
166 public List<File> getSourceFiles(Language... langs) {
167 return toFiles(mainFiles(getLanguageKeys(langs)));
168 }
169
170 @Deprecated
171 public List<File> getJavaSourceFiles() {
172 return getSourceFiles(Java.INSTANCE);
173 }
174
175 public boolean hasJavaSourceFiles() {
176 return !mainFiles(Java.KEY).isEmpty();
177 }
178
179 @Deprecated
180 public List<File> getTestFiles(Language... langs) {
181 return toFiles(testFiles(getLanguageKeys(langs)));
182 }
183
184 @Deprecated
185 public boolean hasTestFiles(Language lang) {
186 return !testFiles(lang.getKey()).isEmpty();
187 }
188
189 private List<InputFile> getFiles(List<File> directories, boolean applyExclusionPatterns, String... langs) {
190 List<InputFile> result = Lists.newArrayList();
191 if (directories == null) {
192 return result;
193 }
194
195 IOFileFilter suffixFilter = getFileSuffixFilter(langs);
196 WildcardPattern[] exclusionPatterns = getExclusionPatterns(applyExclusionPatterns);
197
198 for (File dir : directories) {
199 if (dir.exists()) {
200 IOFileFilter exclusionFilter = new ExclusionFilter(dir, exclusionPatterns);
201 IOFileFilter visibleFileFilter = HiddenFileFilter.VISIBLE;
202 List<IOFileFilter> dirFilters = Lists.newArrayList(visibleFileFilter, suffixFilter, exclusionFilter);
203 dirFilters.addAll(this.filters);
204 List<File> files = (List<File>) FileUtils.listFiles(dir, new AndFileFilter(dirFilters), HiddenFileFilter.VISIBLE);
205 for (File file : files) {
206 String relativePath = DefaultProjectFileSystem.getRelativePath(file, dir);
207 result.add(new DefaultInputFile(dir, relativePath));
208 }
209 }
210 }
211 return result;
212 }
213
214 private WildcardPattern[] getExclusionPatterns(boolean applyExclusionPatterns) {
215 WildcardPattern[] exclusionPatterns;
216 if (applyExclusionPatterns) {
217 exclusionPatterns = WildcardPattern.create(project.getExclusionPatterns());
218 } else {
219 exclusionPatterns = new WildcardPattern[0];
220 }
221 return exclusionPatterns;
222 }
223
224 private IOFileFilter getFileSuffixFilter(String... langKeys) {
225 IOFileFilter suffixFilter = FileFilterUtils.trueFileFilter();
226 if (langKeys != null && langKeys.length > 0) {
227 List<String> suffixes = Arrays.asList(languages.getSuffixes(langKeys));
228 if (!suffixes.isEmpty()) {
229 suffixFilter = new SuffixFileFilter(suffixes);
230 }
231 }
232 return suffixFilter;
233 }
234
235 private static class ExclusionFilter implements IOFileFilter {
236 File sourceDir;
237 WildcardPattern[] patterns;
238
239 ExclusionFilter(File sourceDir, WildcardPattern[] patterns) {
240 this.sourceDir = sourceDir;
241 this.patterns = patterns;
242 }
243
244 public boolean accept(File file) {
245 String relativePath = getRelativePath(file, sourceDir);
246 if (relativePath == null) {
247 return false;
248 }
249 for (WildcardPattern pattern : patterns) {
250 if (pattern.match(relativePath)) {
251 return false;
252 }
253 }
254 return true;
255 }
256
257 public boolean accept(File file, String name) {
258 return accept(file);
259 }
260 }
261
262 public File writeToWorkingDirectory(String content, String fileName) throws IOException {
263 return writeToFile(content, getSonarWorkingDirectory(), fileName);
264 }
265
266 protected static File writeToFile(String content, File dir, String fileName) throws IOException {
267 File file = new File(dir, fileName);
268 FileUtils.writeStringToFile(file, content, CharEncoding.UTF_8);
269 return file;
270 }
271
272 /**
273 * getRelativePath("c:/foo/src/my/package/Hello.java", "c:/foo/src") is "my/package/Hello.java"
274 *
275 * @return null if file is not in dir (including recursive subdirectories)
276 */
277 public static String getRelativePath(File file, File dir) {
278 return getRelativePath(file, Arrays.asList(dir));
279 }
280
281 /**
282 * getRelativePath("c:/foo/src/my/package/Hello.java", ["c:/bar", "c:/foo/src"]) is "my/package/Hello.java".
283 * <p>
284 * Relative path is composed of slashes. Windows backslaches are replaced by /
285 * </p>
286 *
287 * @return null if file is not in dir (including recursive subdirectories)
288 */
289 public static String getRelativePath(File file, List<File> dirs) {
290 List<String> stack = new ArrayList<String>();
291 String path = FilenameUtils.normalize(file.getAbsolutePath());
292 File cursor = new File(path);
293 while (cursor != null) {
294 if (containsFile(dirs, cursor)) {
295 return StringUtils.join(stack, "/");
296 }
297 stack.add(0, cursor.getName());
298 cursor = cursor.getParentFile();
299 }
300 return null;
301 }
302
303 public File getFileFromBuildDirectory(String filename) {
304 File file = new File(getBuildDir(), filename);
305 return (file.exists() ? file : null);
306 }
307
308 public Resource toResource(File file) {
309 if (file == null || !file.exists()) {
310 return null;
311 }
312
313 String relativePath = getRelativePath(file, getSourceDirs());
314 if (relativePath == null) {
315 return null;
316 }
317
318 return (file.isFile() ? new org.sonar.api.resources.File(relativePath) : new org.sonar.api.resources.Directory(relativePath));
319 }
320
321 private static boolean containsFile(List<File> dirs, File cursor) {
322 for (File dir : dirs) {
323 if (FilenameUtils.equalsNormalizedOnSystem(dir.getAbsolutePath(), cursor.getAbsolutePath())) {
324 return true;
325 }
326 }
327 return false;
328 }
329
330 /**
331 * Conversion from Language to key. Allows to provide backward compatibility.
332 */
333 private String[] getLanguageKeys(Language[] langs) {
334 String[] keys = new String[langs.length];
335 for (int i = 0; i < langs.length; i++) {
336 keys[i] = langs[i].getKey();
337 }
338 return keys;
339 }
340
341 /**
342 * Conversion from InputFile to File. Allows to provide backward compatibility.
343 */
344 private static List<File> toFiles(List<InputFile> files) {
345 List<File> result = Lists.newArrayList();
346 for (InputFile file : files) {
347 result.add(file.getFile());
348 }
349 return result;
350 }
351
352 /**
353 * @since 2.6
354 */
355 public List<InputFile> mainFiles(String... langs) {
356 return getFiles(getSourceDirs(), true, langs);
357 }
358
359 /**
360 * @since 2.6
361 */
362 public List<InputFile> testFiles(String... langs) {
363 return getFiles(getTestDirs(), false /* FIXME should be true? */, langs);
364 }
365
366 private static final class DefaultInputFile implements InputFile {
367 private File basedir;
368 private String relativePath;
369
370 DefaultInputFile(File basedir, String relativePath) {
371 this.basedir = basedir;
372 this.relativePath = relativePath;
373 }
374
375 public File getFileBaseDir() {
376 return basedir;
377 }
378
379 public File getFile() {
380 return new File(basedir, relativePath);
381 }
382
383 public String getRelativePath() {
384 return relativePath;
385 }
386 }
387 }