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