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.Path; 028import java.util.ArrayList; 029import java.util.HashMap; 030import java.util.Iterator; 031import java.util.Map; 032import java.util.SortedSet; 033import java.util.TreeSet; 034import java.util.function.Function; 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 Function<FilePredicate, Predicate<InputFile>> defaultPredicateFactory; 058 059 /** 060 * Only for testing 061 */ 062 public DefaultFileSystem(Path baseDir) { 063 this(baseDir, new MapCache()); 064 } 065 066 /** 067 * Only for testing 068 */ 069 public DefaultFileSystem(File baseDir) { 070 this(baseDir.toPath(), new MapCache()); 071 } 072 073 protected DefaultFileSystem(Path baseDir, Cache cache) { 074 this.baseDir = baseDir; 075 this.cache = cache; 076 this.predicates = new DefaultFilePredicates(this.baseDir); 077 } 078 079 public Path baseDirPath() { 080 return baseDir; 081 } 082 083 @Override 084 public File baseDir() { 085 return baseDir.toFile(); 086 } 087 088 public DefaultFileSystem setEncoding(Charset e) { 089 this.encoding = e; 090 return this; 091 } 092 093 @Override 094 public Charset encoding() { 095 return encoding; 096 } 097 098 public DefaultFileSystem setWorkDir(Path d) { 099 this.workDir = d; 100 return this; 101 } 102 103 public DefaultFileSystem setDefaultPredicate(@Nullable Function<FilePredicate, Predicate<InputFile>> defaultPredicateFactory) { 104 this.defaultPredicateFactory = defaultPredicateFactory; 105 return this; 106 } 107 108 @Override 109 public File workDir() { 110 return workDir.toFile(); 111 } 112 113 @Override 114 public InputFile inputFile(FilePredicate predicate) { 115 Iterable<InputFile> files = inputFiles(predicate); 116 Iterator<InputFile> iterator = files.iterator(); 117 if (!iterator.hasNext()) { 118 return null; 119 } 120 InputFile first = iterator.next(); 121 if (!iterator.hasNext()) { 122 return first; 123 } 124 125 StringBuilder sb = new StringBuilder(); 126 sb.append("expected one element but was: <" + first); 127 for (int i = 0; i < 4 && iterator.hasNext(); i++) { 128 sb.append(", " + iterator.next()); 129 } 130 if (iterator.hasNext()) { 131 sb.append(", ..."); 132 } 133 sb.append('>'); 134 135 throw new IllegalArgumentException(sb.toString()); 136 137 } 138 139 /** 140 * Bypass default predicate to get all files/dirs indexed. 141 * Default predicate is used when some files/dirs should not be processed by sensors. 142 */ 143 public Iterable<InputFile> inputFiles() { 144 return OptimizedFilePredicateAdapter.create(predicates.all()).get(cache); 145 } 146 147 @Override 148 public Iterable<InputFile> inputFiles(FilePredicate predicate) { 149 Iterable<InputFile> iterable = OptimizedFilePredicateAdapter.create(predicate).get(cache); 150 if (defaultPredicateFactory != null) { 151 return StreamSupport.stream(iterable.spliterator(), false) 152 .filter(defaultPredicateFactory.apply(predicate)).collect(Collectors.toList()); 153 } 154 return iterable; 155 } 156 157 @Override 158 public boolean hasFiles(FilePredicate predicate) { 159 return inputFiles(predicate).iterator().hasNext(); 160 } 161 162 @Override 163 public Iterable<File> files(FilePredicate predicate) { 164 return () -> StreamSupport.stream(inputFiles(predicate).spliterator(), false) 165 .map(InputFile::file) 166 .iterator(); 167 } 168 169 @Override 170 public InputDir inputDir(File dir) { 171 String relativePath = PathUtils.sanitize(new PathResolver().relativePath(baseDir.toFile(), dir)); 172 if (relativePath == null) { 173 return null; 174 } 175 return cache.inputDir(relativePath); 176 } 177 178 public DefaultFileSystem add(InputFile inputFile) { 179 cache.add(inputFile); 180 return this; 181 } 182 183 public DefaultFileSystem add(DefaultInputDir inputDir) { 184 cache.add(inputDir); 185 return this; 186 } 187 188 @Override 189 public SortedSet<String> languages() { 190 return cache.languages(); 191 } 192 193 @Override 194 public FilePredicates predicates() { 195 return predicates; 196 } 197 198 public abstract static class Cache implements Index { 199 200 protected abstract void doAdd(InputFile inputFile); 201 202 protected abstract void doAdd(InputDir inputDir); 203 204 final void add(InputFile inputFile) { 205 doAdd(inputFile); 206 } 207 208 public void add(InputDir inputDir) { 209 doAdd(inputDir); 210 } 211 212 protected abstract SortedSet<String> languages(); 213 } 214 215 /** 216 * Used only for testing 217 */ 218 private static class MapCache extends Cache { 219 private final Map<String, InputFile> fileMap = new HashMap<>(); 220 private final Map<String, InputDir> dirMap = new HashMap<>(); 221 private final SetMultimap<String, InputFile> filesByNameCache = LinkedHashMultimap.create(); 222 private final SetMultimap<String, InputFile> filesByExtensionCache = LinkedHashMultimap.create(); 223 private SortedSet<String> languages = new TreeSet<>(); 224 225 @Override 226 public Iterable<InputFile> inputFiles() { 227 return new ArrayList<>(fileMap.values()); 228 } 229 230 @Override 231 public InputFile inputFile(String relativePath) { 232 return fileMap.get(relativePath); 233 } 234 235 @Override 236 public InputDir inputDir(String relativePath) { 237 return dirMap.get(relativePath); 238 } 239 240 @Override 241 public Iterable<InputFile> getFilesByName(String filename) { 242 return filesByNameCache.get(filename); 243 } 244 245 @Override 246 public Iterable<InputFile> getFilesByExtension(String extension) { 247 return filesByExtensionCache.get(extension); 248 } 249 250 @Override 251 protected void doAdd(InputFile inputFile) { 252 if (inputFile.language() != null) { 253 languages.add(inputFile.language()); 254 } 255 fileMap.put(inputFile.relativePath(), inputFile); 256 filesByNameCache.put(inputFile.filename(), inputFile); 257 filesByExtensionCache.put(FileExtensionPredicate.getExtension(inputFile), inputFile); 258 } 259 260 @Override 261 protected void doAdd(InputDir inputDir) { 262 dirMap.put(inputDir.relativePath(), inputDir); 263 } 264 265 @Override 266 protected SortedSet<String> languages() { 267 return languages; 268 } 269 } 270 271 @Override 272 public File resolvePath(String path) { 273 File file = new File(path); 274 if (!file.isAbsolute()) { 275 try { 276 file = new File(baseDir(), path).getCanonicalFile(); 277 } catch (IOException e) { 278 throw new IllegalArgumentException("Unable to resolve path '" + path + "'", e); 279 } 280 } 281 return file; 282 } 283}