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