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