001/*
002 * SonarQube
003 * Copyright (C) 2009-2017 SonarSource SA
004 * mailto:info AT sonarsource DOT com
005 *
006 * This program 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 * This program 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 License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api.scan.filesystem;
021
022import java.io.File;
023import java.nio.file.Path;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.List;
027import java.util.Optional;
028import javax.annotation.CheckForNull;
029import javax.annotation.concurrent.Immutable;
030import org.apache.commons.io.FilenameUtils;
031import org.sonar.api.batch.ScannerSide;
032import org.sonar.api.utils.PathUtils;
033
034import static java.util.stream.Collectors.joining;
035
036/**
037 * @since 3.5
038 */
039@ScannerSide
040@Immutable
041public class PathResolver {
042
043  public File relativeFile(File dir, String path) {
044    return dir.toPath().resolve(path).normalize().toFile();
045  }
046
047  public List<File> relativeFiles(File dir, List<String> paths) {
048    List<File> result = new ArrayList<>();
049    for (String path : paths) {
050      result.add(relativeFile(dir, path));
051    }
052    return result;
053  }
054
055  /**
056   * @deprecated since 6.0 was used when component keys were relative to source dirs
057   */
058  @Deprecated
059  @CheckForNull
060  public RelativePath relativePath(Collection<File> dirs, File file) {
061    List<String> stack = new ArrayList<>();
062    File cursor = file;
063    while (cursor != null) {
064      File parentDir = parentDir(dirs, cursor);
065      if (parentDir != null) {
066        return new RelativePath(parentDir, stack.stream().collect(joining("/")));
067      }
068      stack.add(0, cursor.getName());
069      cursor = cursor.getParentFile();
070    }
071    return null;
072  }
073
074  /**
075   * Similar to {@link Path#relativize(Path)} except that:
076   *   <ul>
077   *   <li>null is returned if file is not a child of dir
078   *   <li>the resulting path is converted to use Unix separators
079   *   </ul> 
080   * @since 6.0
081   */
082  @CheckForNull
083  public String relativePath(Path dir, Path file) {
084    Path baseDir = dir.normalize();
085    Path path = file.normalize();
086    if (!path.startsWith(baseDir)) {
087      return null;
088    }
089    try {
090      Path relativized = baseDir.relativize(path);
091      return FilenameUtils.separatorsToUnix(relativized.toString());
092    } catch (IllegalArgumentException e) {
093      return null;
094    }
095  }
096
097  /**
098   * Similar to {@link Path#relativize(Path)} except that:
099   *   <ul>
100   *   <li>Empty is returned if file is not a child of dir
101   *   <li>the resulting path is converted to use Unix separators
102   *   </ul> 
103   * @since 6.6
104   */
105  public static Optional<String> relativize(Path dir, Path file) {
106    Path baseDir = dir.normalize();
107    Path path = file.normalize();
108    if (!path.startsWith(baseDir)) {
109      return Optional.empty();
110    }
111    try {
112      Path relativized = baseDir.relativize(path);
113      return Optional.of(FilenameUtils.separatorsToUnix(relativized.toString()));
114    } catch (IllegalArgumentException e) {
115      return Optional.empty();
116    }
117  }
118
119  @CheckForNull
120  public String relativePath(File dir, File file) {
121    return relativePath(dir.toPath(), file.toPath());
122  }
123
124  @CheckForNull
125  private static File parentDir(Collection<File> dirs, File cursor) {
126    for (File dir : dirs) {
127      if (PathUtils.canonicalPath(dir).equals(PathUtils.canonicalPath(cursor))) {
128        return dir;
129      }
130    }
131    return null;
132  }
133
134  /**
135   * @deprecated since 6.0 was used when component keys were relative to source dirs
136   */
137  @Deprecated
138  public static final class RelativePath {
139    private File dir;
140    private String path;
141
142    public RelativePath(File dir, String path) {
143      this.dir = dir;
144      this.path = path;
145    }
146
147    public File dir() {
148      return dir;
149    }
150
151    public String path() {
152      return path;
153    }
154  }
155}