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