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 org.apache.commons.io.FileUtils;
023 import org.apache.commons.io.FilenameUtils;
024 import org.apache.commons.io.filefilter.*;
025 import org.apache.commons.lang.StringUtils;
026 import org.sonar.api.batch.maven.MavenUtils;
027 import org.sonar.api.utils.SonarException;
028 import org.sonar.api.utils.WildcardPattern;
029
030 import java.io.File;
031 import java.io.IOException;
032 import java.nio.charset.Charset;
033 import java.util.ArrayList;
034 import java.util.Arrays;
035 import java.util.List;
036
037 /**
038 * An implementation of ProjectFileSystem
039 *
040 * @since 1.10
041 */
042 public class DefaultProjectFileSystem implements ProjectFileSystem {
043
044 private Project project;
045
046 /**
047 * Creates a DefaultProjectFileSystem based on a project
048 *
049 * @param project
050 */
051 public DefaultProjectFileSystem(Project project) {
052 this.project = project;
053 }
054
055 /**
056 * Source encoding. Never null, it returns the default plateform charset if it is not defined in project.
057 */
058 public Charset getSourceCharset() {
059 return MavenUtils.getSourceCharset(project.getPom());
060 }
061
062
063 /**
064 * Basedir is the project root directory.
065 */
066 public File getBasedir() {
067 return project.getPom().getBasedir();
068 }
069
070 /**
071 * Build directory is by default "target" in maven projects.
072 */
073 public File getBuildDir() {
074 return resolvePath(project.getPom().getBuild().getDirectory());
075 }
076
077 /**
078 * Directory where classes are placed. By default "target/classes" in maven projects.
079 */
080 public File getBuildOutputDir() {
081 return resolvePath(project.getPom().getBuild().getOutputDirectory());
082 }
083
084 /**
085 * The list of directories for sources
086 */
087 public List<File> getSourceDirs() {
088 return resolvePaths(project.getPom().getCompileSourceRoots());
089 }
090
091 /**
092 * Adds a source directory
093 *
094 * @return the current object
095 */
096 public DefaultProjectFileSystem addSourceDir(File dir) {
097 if (dir == null) {
098 throw new IllegalArgumentException("Can not add null to project source dirs");
099 }
100 project.getPom().getCompileSourceRoots().add(0, dir.getAbsolutePath());
101 return this;
102 }
103
104 /**
105 * The list of directories for tests
106 */
107 public List<File> getTestDirs() {
108 return resolvePaths(project.getPom().getTestCompileSourceRoots());
109 }
110
111 /**
112 * Adds a test directory
113 *
114 * @return the current object
115 */
116 public DefaultProjectFileSystem addTestDir(File dir) {
117 if (dir == null) {
118 throw new IllegalArgumentException("Can not add null to project test dirs");
119 }
120 project.getPom().getTestCompileSourceRoots().add(0, dir.getAbsolutePath());
121 return this;
122 }
123
124 /**
125 * @return the directory where reporting is placed. Default is target/sites
126 */
127 public File getReportOutputDir() {
128 return resolvePath(project.getPom().getReporting().getOutputDirectory());
129 }
130
131 /**
132 * @return the Sonar working directory. Default is "target/sonar"
133 */
134 public File getSonarWorkingDirectory() {
135 try {
136 File dir = new File(project.getPom().getBuild().getDirectory(), "sonar");
137 FileUtils.forceMkdir(dir);
138 return dir;
139
140 } catch (IOException e) {
141 throw new SonarException("Unable to retrieve Sonar working directory.", e);
142 }
143 }
144
145 public File resolvePath(String path) {
146 File file = new File(path);
147 if (!file.isAbsolute()) {
148 file = new File(project.getPom().getBasedir(), path);
149 }
150 return file;
151 }
152
153 private List<File> resolvePaths(List<String> paths) {
154 List<File> result = new ArrayList<File>();
155 if (paths != null) {
156 for (String path : paths) {
157 result.add(resolvePath(path));
158 }
159 }
160
161 return result;
162 }
163
164 /**
165 * Gets the list of source files for given languages
166 *
167 * @param langs language filter. If null or empty, will return empty list
168 */
169 public List<File> getSourceFiles(Language... langs) {
170 return getFiles(getSourceDirs(), true, langs);
171 }
172
173 /**
174 * Gets the list of java source files
175 */
176 public List<File> getJavaSourceFiles() {
177 return getSourceFiles(Java.INSTANCE);
178 }
179
180 /**
181 * @return whether there are java source
182 */
183 public boolean hasJavaSourceFiles() {
184 return !getJavaSourceFiles().isEmpty();
185 }
186
187 /**
188 * Gets the list of test files for given languages
189 *
190 * @param langs language filter. If null or empty, will return empty list
191 */
192 public List<File> getTestFiles(Language... langs) {
193 return getFiles(getTestDirs(), false, langs);
194 }
195
196 /**
197 * @return whether there are tests files
198 */
199 public boolean hasTestFiles(Language lang) {
200 return !getTestFiles(lang).isEmpty();
201 }
202
203 private List<File> getFiles(List<File> directories, boolean applyExclusionPatterns, Language... langs) {
204 List<File> result = new ArrayList<File>();
205 if (directories == null || langs == null) {
206 return result;
207 }
208
209 IOFileFilter suffixFilter = getFileSuffixFilter(langs);
210 WildcardPattern[] exclusionPatterns = getExclusionPatterns(applyExclusionPatterns);
211
212 for (File dir : directories) {
213 if (dir.exists()) {
214 IOFileFilter exclusionFilter = new ExclusionFilter(dir, exclusionPatterns);
215 result.addAll(FileUtils.listFiles(dir, new AndFileFilter(suffixFilter, exclusionFilter), TrueFileFilter.INSTANCE));
216 }
217 }
218 return result;
219 }
220
221 private WildcardPattern[] getExclusionPatterns(boolean applyExclusionPatterns) {
222 WildcardPattern[] exclusionPatterns;
223 if (applyExclusionPatterns) {
224 exclusionPatterns = WildcardPattern.create(project.getExclusionPatterns());
225 } else {
226 exclusionPatterns = new WildcardPattern[0];
227 }
228 return exclusionPatterns;
229 }
230
231 private IOFileFilter getFileSuffixFilter(Language... langs) {
232 IOFileFilter suffixFilter;
233 if (langs.length == 0) {
234 suffixFilter = FileFilterUtils.trueFileFilter();
235
236 } else {
237 List<String> suffixes = new ArrayList<String>();
238 for (Language lang : langs) {
239 if (lang.getFileSuffixes() != null) {
240 suffixes.addAll(Arrays.asList(lang.getFileSuffixes()));
241 }
242 }
243 suffixFilter = new SuffixFileFilter(suffixes);
244 }
245 return suffixFilter;
246 }
247
248 private static class ExclusionFilter implements IOFileFilter {
249 File sourceDir;
250 WildcardPattern[] patterns;
251
252 ExclusionFilter(File sourceDir, WildcardPattern[] patterns) {
253 this.sourceDir = sourceDir;
254 this.patterns = patterns;
255 }
256
257 public boolean accept(File file) {
258 String relativePath = getRelativePath(file, sourceDir);
259 if (relativePath == null) {
260 return false;
261 }
262 for (WildcardPattern pattern : patterns) {
263 if (pattern.match(relativePath)) {
264 return false;
265 }
266 }
267 return true;
268 }
269
270 public boolean accept(File file, String name) {
271 return accept(file);
272 }
273 }
274
275 /**
276 * Save data into a new file of Sonar working directory.
277 *
278 * @return the created file
279 */
280 public File writeToWorkingDirectory(String content, String fileName) throws IOException {
281 return writeToFile(content, getSonarWorkingDirectory(), fileName);
282 }
283
284 protected static File writeToFile(String content, File dir, String fileName) throws IOException {
285 File file = new File(dir, fileName);
286 FileUtils.writeStringToFile(file, content, "UTF-8");
287 return file;
288 }
289
290 /**
291 * getRelativePath("c:/foo/src/my/package/Hello.java", "c:/foo/src") is "my/package/Hello.java"
292 *
293 * @return null if file is not in dir (including recursive subdirectories)
294 */
295 public static String getRelativePath(File file, File dir) {
296 return getRelativePath(file, Arrays.asList(dir));
297 }
298
299 /**
300 * getRelativePath("c:/foo/src/my/package/Hello.java", ["c:/bar", "c:/foo/src"]) is "my/package/Hello.java".
301 * <p/>
302 * <p>Relative path is composed of slashes. Windows backslaches are replaced by /</p>
303 *
304 * @return null if file is not in dir (including recursive subdirectories)
305 */
306 public static String getRelativePath(File file, List<File> dirs) {
307 List<String> stack = new ArrayList<String>();
308 String path = FilenameUtils.normalize(file.getAbsolutePath());
309 File cursor = new File(path);
310 while (cursor != null) {
311 if (containsFile(dirs, cursor)) {
312 return StringUtils.join(stack, "/");
313 }
314 stack.add(0, cursor.getName());
315 cursor = cursor.getParentFile();
316 }
317 return null;
318 }
319
320 public File getFileFromBuildDirectory(String filename) {
321 File file = new File(getBuildDir(), filename);
322 return (file.exists() ? file : null);
323 }
324
325 public Resource toResource(File file) {
326 if (file == null || !file.exists()) {
327 return null;
328 }
329
330 String relativePath = getRelativePath(file, getSourceDirs());
331 if (relativePath == null) {
332 return null;
333 }
334
335 return (file.isFile() ? new org.sonar.api.resources.File(relativePath) : new org.sonar.api.resources.Directory(relativePath));
336 }
337
338 private static boolean containsFile(List<File> dirs, File cursor) {
339 for (File dir : dirs) {
340 if (FilenameUtils.equalsNormalizedOnSystem(dir.getAbsolutePath(), cursor.getAbsolutePath())) {
341 return true;
342 }
343 }
344 return false;
345 }
346
347 }