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 }