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