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