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