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