001/* 002 * SonarQube 003 * Copyright (C) 2009-2016 SonarSource SA 004 * mailto:contact 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.base.Function; 023import com.google.common.collect.Iterables; 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.Collections; 031import java.util.HashMap; 032import java.util.Iterator; 033import java.util.Map; 034import java.util.SortedSet; 035import java.util.TreeSet; 036import javax.annotation.CheckForNull; 037import javax.annotation.Nullable; 038import org.sonar.api.batch.fs.FilePredicate; 039import org.sonar.api.batch.fs.FilePredicates; 040import org.sonar.api.batch.fs.FileSystem; 041import org.sonar.api.batch.fs.InputDir; 042import org.sonar.api.batch.fs.InputFile; 043import org.sonar.api.scan.filesystem.PathResolver; 044import org.sonar.api.utils.PathUtils; 045 046/** 047 * @since 4.2 048 */ 049public class DefaultFileSystem implements FileSystem { 050 051 private final Cache cache; 052 private final SortedSet<String> languages = new TreeSet<>(); 053 private final Path baseDir; 054 private Path workDir; 055 private Charset encoding; 056 protected final FilePredicates predicates; 057 private FilePredicate 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 FilePredicate 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 FilePredicate combinedPredicate = predicate; 161 if (defaultPredicate != null) { 162 combinedPredicate = predicates().and(defaultPredicate, predicate); 163 } 164 return OptimizedFilePredicateAdapter.create(combinedPredicate).get(cache); 165 } 166 167 @Override 168 public boolean hasFiles(FilePredicate predicate) { 169 return inputFiles(predicate).iterator().hasNext(); 170 } 171 172 @Override 173 public Iterable<File> files(FilePredicate predicate) { 174 doPreloadFiles(); 175 return Iterables.transform(inputFiles(predicate), new Function<InputFile, File>() { 176 @Override 177 public File apply(InputFile input) { 178 return input.file(); 179 } 180 }); 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 /** 194 * Adds InputFile to the list and registers its language, if present. 195 * Synchronized because PersistIt Exchange is not concurrent 196 */ 197 public synchronized DefaultFileSystem add(DefaultInputFile inputFile) { 198 if (this.baseDir == null) { 199 throw new IllegalStateException("Please set basedir on filesystem before adding files"); 200 } 201 inputFile.setModuleBaseDir(this.baseDir); 202 cache.add(inputFile); 203 String language = inputFile.language(); 204 if (language != null) { 205 languages.add(language); 206 } 207 return this; 208 } 209 210 /** 211 * Adds InputDir to the list. 212 * Synchronized because PersistIt Exchange is not concurrent 213 */ 214 public synchronized DefaultFileSystem add(DefaultInputDir inputDir) { 215 if (this.baseDir == null) { 216 throw new IllegalStateException("Please set basedir on filesystem before adding dirs"); 217 } 218 inputDir.setModuleBaseDir(this.baseDir); 219 cache.add(inputDir); 220 return this; 221 } 222 223 /** 224 * Adds a language to the list. To be used only for unit tests that need to use {@link #languages()} without 225 * using {@link #add(DefaultInputFile)}. 226 */ 227 public DefaultFileSystem addLanguages(String language, String... others) { 228 languages.add(language); 229 Collections.addAll(languages, others); 230 return this; 231 } 232 233 @Override 234 public SortedSet<String> languages() { 235 doPreloadFiles(); 236 return languages; 237 } 238 239 @Override 240 public FilePredicates predicates() { 241 return predicates; 242 } 243 244 /** 245 * This method is called before each search of files. 246 */ 247 protected void doPreloadFiles() { 248 // nothing to do by default 249 } 250 251 public abstract static class Cache implements Index { 252 @Override 253 public abstract Iterable<InputFile> inputFiles(); 254 255 @Override 256 @CheckForNull 257 public abstract InputFile inputFile(String relativePath); 258 259 @Override 260 @CheckForNull 261 public abstract InputDir inputDir(String relativePath); 262 263 protected abstract void doAdd(InputFile inputFile); 264 265 protected abstract void doAdd(InputDir inputDir); 266 267 final void add(InputFile inputFile) { 268 doAdd(inputFile); 269 } 270 271 public void add(InputDir inputDir) { 272 doAdd(inputDir); 273 } 274 275 } 276 277 /** 278 * Used only for testing 279 */ 280 private static class MapCache extends Cache { 281 private final Map<String, InputFile> fileMap = new HashMap<>(); 282 private final Map<String, InputDir> dirMap = new HashMap<>(); 283 284 @Override 285 public Iterable<InputFile> inputFiles() { 286 return new ArrayList<>(fileMap.values()); 287 } 288 289 @Override 290 public InputFile inputFile(String relativePath) { 291 return fileMap.get(relativePath); 292 } 293 294 @Override 295 public InputDir inputDir(String relativePath) { 296 return dirMap.get(relativePath); 297 } 298 299 @Override 300 protected void doAdd(InputFile inputFile) { 301 fileMap.put(inputFile.relativePath(), inputFile); 302 } 303 304 @Override 305 protected void doAdd(InputDir inputDir) { 306 dirMap.put(inputDir.relativePath(), inputDir); 307 } 308 } 309 310 @Override 311 public File resolvePath(String path) { 312 File file = new File(path); 313 if (!file.isAbsolute()) { 314 try { 315 file = new File(baseDir(), path).getCanonicalFile(); 316 } catch (IOException e) { 317 throw new IllegalArgumentException("Unable to resolve path '" + path + "'", e); 318 } 319 } 320 return file; 321 } 322}