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}