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.batch.fs.internal; 021 022import com.google.common.collect.Iterables; 023import com.google.common.collect.LinkedHashMultimap; 024import com.google.common.collect.SetMultimap; 025import java.io.File; 026import java.io.IOException; 027import java.nio.charset.Charset; 028import java.nio.file.LinkOption; 029import java.nio.file.Path; 030import java.util.ArrayList; 031import java.util.HashMap; 032import java.util.Iterator; 033import java.util.Map; 034import java.util.SortedSet; 035import java.util.TreeSet; 036import java.util.function.Predicate; 037import java.util.stream.Collectors; 038import java.util.stream.StreamSupport; 039 040import javax.annotation.Nullable; 041import org.sonar.api.batch.fs.FilePredicate; 042import org.sonar.api.batch.fs.FilePredicates; 043import org.sonar.api.batch.fs.FileSystem; 044import org.sonar.api.batch.fs.InputDir; 045import org.sonar.api.batch.fs.InputFile; 046import org.sonar.api.scan.filesystem.PathResolver; 047import org.sonar.api.utils.PathUtils; 048 049/** 050 * @since 4.2 051 */ 052public class DefaultFileSystem implements FileSystem { 053 054 private final Cache cache; 055 private final Path baseDir; 056 private Path workDir; 057 private Charset encoding; 058 protected final FilePredicates predicates; 059 private Predicate<InputFile> defaultPredicate; 060 061 /** 062 * Only for testing 063 */ 064 public DefaultFileSystem(Path baseDir) { 065 this(baseDir.toFile(), new MapCache()); 066 } 067 068 /** 069 * Only for testing 070 */ 071 public DefaultFileSystem(File baseDir) { 072 this(baseDir, new MapCache()); 073 } 074 075 protected DefaultFileSystem(@Nullable File baseDir, Cache cache) { 076 // Basedir can be null with views 077 try { 078 this.baseDir = baseDir != null ? baseDir.toPath().toRealPath(LinkOption.NOFOLLOW_LINKS) : new File(".").toPath().toAbsolutePath().normalize(); 079 } catch (IOException e) { 080 throw new IllegalStateException(e); 081 } 082 this.cache = cache; 083 this.predicates = new DefaultFilePredicates(this.baseDir); 084 } 085 086 public Path baseDirPath() { 087 return baseDir; 088 } 089 090 @Override 091 public File baseDir() { 092 return baseDir.toFile(); 093 } 094 095 public DefaultFileSystem setEncoding(@Nullable Charset e) { 096 this.encoding = e; 097 return this; 098 } 099 100 @Override 101 public Charset encoding() { 102 return encoding == null ? Charset.defaultCharset() : encoding; 103 } 104 105 public boolean isDefaultJvmEncoding() { 106 return encoding == null; 107 } 108 109 public DefaultFileSystem setWorkDir(File d) { 110 this.workDir = d.getAbsoluteFile().toPath().normalize(); 111 return this; 112 } 113 114 public DefaultFileSystem setDefaultPredicate(@Nullable Predicate<InputFile> predicate) { 115 this.defaultPredicate = predicate; 116 return this; 117 } 118 119 @Override 120 public File workDir() { 121 return workDir.toFile(); 122 } 123 124 @Override 125 public InputFile inputFile(FilePredicate predicate) { 126 Iterable<InputFile> files = inputFiles(predicate); 127 Iterator<InputFile> iterator = files.iterator(); 128 if (!iterator.hasNext()) { 129 return null; 130 } 131 InputFile first = iterator.next(); 132 if (!iterator.hasNext()) { 133 return first; 134 } 135 136 StringBuilder sb = new StringBuilder(); 137 sb.append("expected one element but was: <" + first); 138 for (int i = 0; i < 4 && iterator.hasNext(); i++) { 139 sb.append(", " + iterator.next()); 140 } 141 if (iterator.hasNext()) { 142 sb.append(", ..."); 143 } 144 sb.append('>'); 145 146 throw new IllegalArgumentException(sb.toString()); 147 148 } 149 150 /** 151 * Bypass default predicate to get all files/dirs indexed. 152 * Default predicate is used when some files/dirs should not be processed by sensors. 153 */ 154 public Iterable<InputFile> inputFiles() { 155 doPreloadFiles(); 156 return OptimizedFilePredicateAdapter.create(predicates.all()).get(cache); 157 } 158 159 @Override 160 public Iterable<InputFile> inputFiles(FilePredicate predicate) { 161 doPreloadFiles(); 162 Iterable<InputFile> iterable = OptimizedFilePredicateAdapter.create(predicate).get(cache); 163 if (defaultPredicate != null) { 164 return StreamSupport.stream(iterable.spliterator(), false) 165 .filter(defaultPredicate::test).collect(Collectors.toList()); 166 } 167 return iterable; 168 } 169 170 @Override 171 public boolean hasFiles(FilePredicate predicate) { 172 return inputFiles(predicate).iterator().hasNext(); 173 } 174 175 @Override 176 public Iterable<File> files(FilePredicate predicate) { 177 doPreloadFiles(); 178 return Iterables.transform(inputFiles(predicate), InputFile::file); 179 } 180 181 @Override 182 public InputDir inputDir(File dir) { 183 doPreloadFiles(); 184 String relativePath = PathUtils.sanitize(new PathResolver().relativePath(baseDir.toFile(), dir)); 185 if (relativePath == null) { 186 return null; 187 } 188 return cache.inputDir(relativePath); 189 } 190 191 public DefaultFileSystem add(InputFile inputFile) { 192 cache.add(inputFile); 193 return this; 194 } 195 196 public DefaultFileSystem add(DefaultInputDir inputDir) { 197 cache.add(inputDir); 198 return this; 199 } 200 201 @Override 202 public SortedSet<String> languages() { 203 doPreloadFiles(); 204 return cache.languages(); 205 } 206 207 @Override 208 public FilePredicates predicates() { 209 return predicates; 210 } 211 212 /** 213 * This method is called before each search of files. 214 */ 215 protected void doPreloadFiles() { 216 // nothing to do by default 217 } 218 219 public abstract static class Cache implements Index { 220 221 protected abstract void doAdd(InputFile inputFile); 222 223 protected abstract void doAdd(InputDir inputDir); 224 225 final void add(InputFile inputFile) { 226 doAdd(inputFile); 227 } 228 229 public void add(InputDir inputDir) { 230 doAdd(inputDir); 231 } 232 233 protected abstract SortedSet<String> languages(); 234 } 235 236 /** 237 * Used only for testing 238 */ 239 private static class MapCache extends Cache { 240 private final Map<String, InputFile> fileMap = new HashMap<>(); 241 private final Map<String, InputDir> dirMap = new HashMap<>(); 242 private final SetMultimap<String, InputFile> filesByNameCache = LinkedHashMultimap.create(); 243 private final SetMultimap<String, InputFile> filesByExtensionCache = LinkedHashMultimap.create(); 244 private SortedSet<String> languages = new TreeSet<>(); 245 246 @Override 247 public Iterable<InputFile> inputFiles() { 248 return new ArrayList<>(fileMap.values()); 249 } 250 251 @Override 252 public InputFile inputFile(String relativePath) { 253 return fileMap.get(relativePath); 254 } 255 256 @Override 257 public InputDir inputDir(String relativePath) { 258 return dirMap.get(relativePath); 259 } 260 261 @Override 262 public Iterable<InputFile> getFilesByName(String filename) { 263 return filesByNameCache.get(filename); 264 } 265 266 @Override 267 public Iterable<InputFile> getFilesByExtension(String extension) { 268 return filesByExtensionCache.get(extension); 269 } 270 271 @Override 272 protected void doAdd(InputFile inputFile) { 273 if (inputFile.language() != null) { 274 languages.add(inputFile.language()); 275 } 276 fileMap.put(inputFile.relativePath(), inputFile); 277 filesByNameCache.put(FilenamePredicate.getFilename(inputFile), inputFile); 278 filesByExtensionCache.put(FileExtensionPredicate.getExtension(inputFile), inputFile); 279 } 280 281 @Override 282 protected void doAdd(InputDir inputDir) { 283 dirMap.put(inputDir.relativePath(), inputDir); 284 } 285 286 @Override 287 protected SortedSet<String> languages() { 288 return languages; 289 } 290 } 291 292 @Override 293 public File resolvePath(String path) { 294 File file = new File(path); 295 if (!file.isAbsolute()) { 296 try { 297 file = new File(baseDir(), path).getCanonicalFile(); 298 } catch (IOException e) { 299 throw new IllegalArgumentException("Unable to resolve path '" + path + "'", e); 300 } 301 } 302 return file; 303 } 304}