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