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