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.LinkedHashMultimap; 023import com.google.common.collect.SetMultimap; 024import java.io.File; 025import java.io.IOException; 026import java.nio.charset.Charset; 027import java.nio.file.LinkOption; 028import java.nio.file.Path; 029import java.util.ArrayList; 030import java.util.HashMap; 031import java.util.Iterator; 032import java.util.Map; 033import java.util.SortedSet; 034import java.util.TreeSet; 035import java.util.function.Predicate; 036import java.util.stream.Collectors; 037import java.util.stream.StreamSupport; 038import javax.annotation.Nullable; 039import org.sonar.api.batch.fs.FilePredicate; 040import org.sonar.api.batch.fs.FilePredicates; 041import org.sonar.api.batch.fs.FileSystem; 042import org.sonar.api.batch.fs.InputDir; 043import org.sonar.api.batch.fs.InputFile; 044import org.sonar.api.scan.filesystem.PathResolver; 045import org.sonar.api.utils.PathUtils; 046 047/** 048 * @since 4.2 049 */ 050public class DefaultFileSystem implements FileSystem { 051 052 private final Cache cache; 053 private final Path baseDir; 054 private Path workDir; 055 private Charset encoding; 056 protected final FilePredicates predicates; 057 private Predicate<InputFile> defaultPredicate; 058 059 /** 060 * Only for testing 061 */ 062 public DefaultFileSystem(Path baseDir) { 063 this(baseDir.toFile(), new MapCache()); 064 } 065 066 /** 067 * Only for testing 068 */ 069 public DefaultFileSystem(File baseDir) { 070 this(baseDir, new MapCache()); 071 } 072 073 protected DefaultFileSystem(@Nullable File baseDir, Cache cache) { 074 // Basedir can be null with views 075 try { 076 this.baseDir = baseDir != null ? baseDir.toPath().toRealPath(LinkOption.NOFOLLOW_LINKS) : new File(".").toPath().toAbsolutePath().normalize(); 077 } catch (IOException e) { 078 throw new IllegalStateException(e); 079 } 080 this.cache = cache; 081 this.predicates = new DefaultFilePredicates(this.baseDir); 082 } 083 084 public Path baseDirPath() { 085 return baseDir; 086 } 087 088 @Override 089 public File baseDir() { 090 return baseDir.toFile(); 091 } 092 093 public DefaultFileSystem setEncoding(@Nullable Charset e) { 094 this.encoding = e; 095 return this; 096 } 097 098 @Override 099 public Charset encoding() { 100 return encoding == null ? Charset.defaultCharset() : encoding; 101 } 102 103 public boolean isDefaultJvmEncoding() { 104 return encoding == null; 105 } 106 107 public DefaultFileSystem setWorkDir(File d) { 108 this.workDir = d.getAbsoluteFile().toPath().normalize(); 109 return this; 110 } 111 112 public DefaultFileSystem setDefaultPredicate(@Nullable Predicate<InputFile> predicate) { 113 this.defaultPredicate = predicate; 114 return this; 115 } 116 117 @Override 118 public File workDir() { 119 return workDir.toFile(); 120 } 121 122 @Override 123 public InputFile inputFile(FilePredicate predicate) { 124 Iterable<InputFile> files = inputFiles(predicate); 125 Iterator<InputFile> iterator = files.iterator(); 126 if (!iterator.hasNext()) { 127 return null; 128 } 129 InputFile first = iterator.next(); 130 if (!iterator.hasNext()) { 131 return first; 132 } 133 134 StringBuilder sb = new StringBuilder(); 135 sb.append("expected one element but was: <" + first); 136 for (int i = 0; i < 4 && iterator.hasNext(); i++) { 137 sb.append(", " + iterator.next()); 138 } 139 if (iterator.hasNext()) { 140 sb.append(", ..."); 141 } 142 sb.append('>'); 143 144 throw new IllegalArgumentException(sb.toString()); 145 146 } 147 148 /** 149 * Bypass default predicate to get all files/dirs indexed. 150 * Default predicate is used when some files/dirs should not be processed by sensors. 151 */ 152 public Iterable<InputFile> inputFiles() { 153 doPreloadFiles(); 154 return OptimizedFilePredicateAdapter.create(predicates.all()).get(cache); 155 } 156 157 @Override 158 public Iterable<InputFile> inputFiles(FilePredicate predicate) { 159 doPreloadFiles(); 160 Iterable<InputFile> iterable = OptimizedFilePredicateAdapter.create(predicate).get(cache); 161 if (defaultPredicate != null) { 162 return StreamSupport.stream(iterable.spliterator(), false) 163 .filter(defaultPredicate::test).collect(Collectors.toList()); 164 } 165 return iterable; 166 } 167 168 @Override 169 public boolean hasFiles(FilePredicate predicate) { 170 return inputFiles(predicate).iterator().hasNext(); 171 } 172 173 @Override 174 public Iterable<File> files(FilePredicate predicate) { 175 doPreloadFiles(); 176 return () -> StreamSupport.stream(inputFiles(predicate).spliterator(), false) 177 .map(InputFile::file) 178 .iterator(); 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}