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 }