001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2011 SonarSource 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.CoreProperties; 029 import org.sonar.api.batch.FileFilter; 030 import org.sonar.api.utils.Logs; 031 import org.sonar.api.utils.SonarException; 032 import org.sonar.api.utils.WildcardPattern; 033 034 import java.io.File; 035 import java.io.IOException; 036 import java.nio.charset.Charset; 037 import java.util.ArrayList; 038 import java.util.Arrays; 039 import java.util.List; 040 041 /** 042 * An implementation of {@link ProjectFileSystem}. 043 * For internal use only. 044 * 045 * @since 1.10 046 * @TODO in fact this class should not be located in sonar-plugin-api 047 */ 048 public class DefaultProjectFileSystem implements ProjectFileSystem { 049 050 private Project project; 051 private Languages languages; 052 private List<IOFileFilter> filters = Lists.newArrayList(); 053 054 public DefaultProjectFileSystem(Project project, Languages languages) { 055 this.project = project; 056 this.languages = languages; 057 } 058 059 public DefaultProjectFileSystem(Project project, Languages languages, FileFilter... fileFilters) { 060 this(project, languages); 061 for (FileFilter fileFilter : fileFilters) { 062 filters.add(new DelegateFileFilter(fileFilter)); 063 } 064 } 065 066 public Charset getSourceCharset() { 067 String encoding = project.getConfiguration().getString(CoreProperties.ENCODING_PROPERTY); 068 if (StringUtils.isNotEmpty(encoding)) { 069 try { 070 return Charset.forName(encoding); 071 } catch (Exception e) { 072 Logs.INFO.warn("Can not get project charset", e); 073 } 074 } 075 return Charset.defaultCharset(); 076 } 077 078 public File getBasedir() { 079 return project.getPom().getBasedir(); 080 } 081 082 public File getBuildDir() { 083 return resolvePath(project.getPom().getBuild().getDirectory()); 084 } 085 086 public File getBuildOutputDir() { 087 return resolvePath(project.getPom().getBuild().getOutputDirectory()); 088 } 089 090 /** 091 * Maven can modify source directories during Sonar execution - see MavenPhaseExecutor. 092 */ 093 public List<File> getSourceDirs() { 094 return resolvePaths(project.getPom().getCompileSourceRoots()); 095 } 096 097 /** 098 * @deprecated since 2.6, because should be immutable 099 */ 100 @Deprecated 101 public DefaultProjectFileSystem addSourceDir(File dir) { 102 if (dir == null) { 103 throw new IllegalArgumentException("Can not add null to project source dirs"); 104 } 105 project.getPom().getCompileSourceRoots().add(0, dir.getAbsolutePath()); 106 return this; 107 } 108 109 /** 110 * Maven can modify test directories during Sonar execution - see MavenPhaseExecutor. 111 */ 112 public List<File> getTestDirs() { 113 return resolvePaths(project.getPom().getTestCompileSourceRoots()); 114 } 115 116 /** 117 * @deprecated since 2.6, because should be immutable 118 */ 119 @Deprecated 120 public DefaultProjectFileSystem addTestDir(File dir) { 121 if (dir == null) { 122 throw new IllegalArgumentException("Can not add null to project test dirs"); 123 } 124 project.getPom().getTestCompileSourceRoots().add(0, dir.getAbsolutePath()); 125 return this; 126 } 127 128 public File getReportOutputDir() { 129 return resolvePath(project.getPom().getReporting().getOutputDirectory()); 130 } 131 132 public File getSonarWorkingDirectory() { 133 try { 134 File dir = new File(getBuildDir(), "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 try { 147 file = new File(getBasedir(), path).getCanonicalFile(); 148 } catch (IOException e) { 149 throw new SonarException("Unable to resolve path '" + path + "'", e); 150 } 151 } 152 return file; 153 } 154 155 private List<File> resolvePaths(List<String> paths) { 156 List<File> result = Lists.newArrayList(); 157 if (paths != null) { 158 for (String path : paths) { 159 result.add(resolvePath(path)); 160 } 161 } 162 return result; 163 } 164 165 @Deprecated 166 public List<File> getSourceFiles(Language... langs) { 167 return toFiles(mainFiles(getLanguageKeys(langs))); 168 } 169 170 @Deprecated 171 public List<File> getJavaSourceFiles() { 172 return getSourceFiles(Java.INSTANCE); 173 } 174 175 public boolean hasJavaSourceFiles() { 176 return !mainFiles(Java.KEY).isEmpty(); 177 } 178 179 @Deprecated 180 public List<File> getTestFiles(Language... langs) { 181 return toFiles(testFiles(getLanguageKeys(langs))); 182 } 183 184 @Deprecated 185 public boolean hasTestFiles(Language lang) { 186 return !testFiles(lang.getKey()).isEmpty(); 187 } 188 189 private List<InputFile> getFiles(List<File> directories, boolean applyExclusionPatterns, String... langs) { 190 List<InputFile> result = Lists.newArrayList(); 191 if (directories == null) { 192 return result; 193 } 194 195 IOFileFilter suffixFilter = getFileSuffixFilter(langs); 196 WildcardPattern[] exclusionPatterns = getExclusionPatterns(applyExclusionPatterns); 197 198 for (File dir : directories) { 199 if (dir.exists()) { 200 IOFileFilter exclusionFilter = new ExclusionFilter(dir, exclusionPatterns); 201 IOFileFilter visibleFileFilter = HiddenFileFilter.VISIBLE; 202 List<IOFileFilter> dirFilters = Lists.newArrayList(visibleFileFilter, suffixFilter, exclusionFilter); 203 dirFilters.addAll(this.filters); 204 List<File> files = (List<File>) FileUtils.listFiles(dir, new AndFileFilter(dirFilters), HiddenFileFilter.VISIBLE); 205 for (File file : files) { 206 String relativePath = DefaultProjectFileSystem.getRelativePath(file, dir); 207 result.add(new DefaultInputFile(dir, relativePath)); 208 } 209 } 210 } 211 return result; 212 } 213 214 private WildcardPattern[] getExclusionPatterns(boolean applyExclusionPatterns) { 215 WildcardPattern[] exclusionPatterns; 216 if (applyExclusionPatterns) { 217 exclusionPatterns = WildcardPattern.create(project.getExclusionPatterns()); 218 } else { 219 exclusionPatterns = new WildcardPattern[0]; 220 } 221 return exclusionPatterns; 222 } 223 224 private IOFileFilter getFileSuffixFilter(String... langKeys) { 225 IOFileFilter suffixFilter = FileFilterUtils.trueFileFilter(); 226 if (langKeys != null && langKeys.length > 0) { 227 List<String> suffixes = Arrays.asList(languages.getSuffixes(langKeys)); 228 if (!suffixes.isEmpty()) { 229 suffixFilter = new SuffixFileFilter(suffixes); 230 } 231 } 232 return suffixFilter; 233 } 234 235 private static class ExclusionFilter implements IOFileFilter { 236 File sourceDir; 237 WildcardPattern[] patterns; 238 239 ExclusionFilter(File sourceDir, WildcardPattern[] patterns) { 240 this.sourceDir = sourceDir; 241 this.patterns = patterns; 242 } 243 244 public boolean accept(File file) { 245 String relativePath = getRelativePath(file, sourceDir); 246 if (relativePath == null) { 247 return false; 248 } 249 for (WildcardPattern pattern : patterns) { 250 if (pattern.match(relativePath)) { 251 return false; 252 } 253 } 254 return true; 255 } 256 257 public boolean accept(File file, String name) { 258 return accept(file); 259 } 260 } 261 262 public File writeToWorkingDirectory(String content, String fileName) throws IOException { 263 return writeToFile(content, getSonarWorkingDirectory(), fileName); 264 } 265 266 protected static File writeToFile(String content, File dir, String fileName) throws IOException { 267 File file = new File(dir, fileName); 268 FileUtils.writeStringToFile(file, content, CharEncoding.UTF_8); 269 return file; 270 } 271 272 /** 273 * getRelativePath("c:/foo/src/my/package/Hello.java", "c:/foo/src") is "my/package/Hello.java" 274 * 275 * @return null if file is not in dir (including recursive subdirectories) 276 */ 277 public static String getRelativePath(File file, File dir) { 278 return getRelativePath(file, Arrays.asList(dir)); 279 } 280 281 /** 282 * getRelativePath("c:/foo/src/my/package/Hello.java", ["c:/bar", "c:/foo/src"]) is "my/package/Hello.java". 283 * <p> 284 * Relative path is composed of slashes. Windows backslaches are replaced by / 285 * </p> 286 * 287 * @return null if file is not in dir (including recursive subdirectories) 288 */ 289 public static String getRelativePath(File file, List<File> dirs) { 290 List<String> stack = new ArrayList<String>(); 291 String path = FilenameUtils.normalize(file.getAbsolutePath()); 292 File cursor = new File(path); 293 while (cursor != null) { 294 if (containsFile(dirs, cursor)) { 295 return StringUtils.join(stack, "/"); 296 } 297 stack.add(0, cursor.getName()); 298 cursor = cursor.getParentFile(); 299 } 300 return null; 301 } 302 303 public File getFileFromBuildDirectory(String filename) { 304 File file = new File(getBuildDir(), filename); 305 return (file.exists() ? file : null); 306 } 307 308 public Resource toResource(File file) { 309 if (file == null || !file.exists()) { 310 return null; 311 } 312 313 String relativePath = getRelativePath(file, getSourceDirs()); 314 if (relativePath == null) { 315 return null; 316 } 317 318 return (file.isFile() ? new org.sonar.api.resources.File(relativePath) : new org.sonar.api.resources.Directory(relativePath)); 319 } 320 321 private static boolean containsFile(List<File> dirs, File cursor) { 322 for (File dir : dirs) { 323 if (FilenameUtils.equalsNormalizedOnSystem(dir.getAbsolutePath(), cursor.getAbsolutePath())) { 324 return true; 325 } 326 } 327 return false; 328 } 329 330 /** 331 * Conversion from Language to key. Allows to provide backward compatibility. 332 */ 333 private String[] getLanguageKeys(Language[] langs) { 334 String[] keys = new String[langs.length]; 335 for (int i = 0; i < langs.length; i++) { 336 keys[i] = langs[i].getKey(); 337 } 338 return keys; 339 } 340 341 /** 342 * Conversion from InputFile to File. Allows to provide backward compatibility. 343 */ 344 private static List<File> toFiles(List<InputFile> files) { 345 List<File> result = Lists.newArrayList(); 346 for (InputFile file : files) { 347 result.add(file.getFile()); 348 } 349 return result; 350 } 351 352 /** 353 * @since 2.6 354 */ 355 public List<InputFile> mainFiles(String... langs) { 356 return getFiles(getSourceDirs(), true, langs); 357 } 358 359 /** 360 * @since 2.6 361 */ 362 public List<InputFile> testFiles(String... langs) { 363 return getFiles(getTestDirs(), false /* FIXME should be true? */, langs); 364 } 365 366 private static final class DefaultInputFile implements InputFile { 367 private File basedir; 368 private String relativePath; 369 370 DefaultInputFile(File basedir, String relativePath) { 371 this.basedir = basedir; 372 this.relativePath = relativePath; 373 } 374 375 public File getFileBaseDir() { 376 return basedir; 377 } 378 379 public File getFile() { 380 return new File(basedir, relativePath); 381 } 382 383 public String getRelativePath() { 384 return relativePath; 385 } 386 } 387 }