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