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