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 }