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<String>(); 054 private final Path baseDir; 055 private Path workDir; 056 private Charset encoding; 057 private final FilePredicates predicates; 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 this.baseDir = baseDir != null ? baseDir.toPath().toAbsolutePath().normalize() : new File(".").toPath(); 076 this.cache = cache; 077 this.predicates = new DefaultFilePredicates(this.baseDir); 078 } 079 080 public Path baseDirPath() { 081 return baseDir; 082 } 083 084 @Override 085 public File baseDir() { 086 return baseDir.toFile(); 087 } 088 089 public DefaultFileSystem setEncoding(@Nullable Charset e) { 090 this.encoding = e; 091 return this; 092 } 093 094 @Override 095 public Charset encoding() { 096 return encoding == null ? Charset.defaultCharset() : encoding; 097 } 098 099 public boolean isDefaultJvmEncoding() { 100 return encoding == null; 101 } 102 103 public DefaultFileSystem setWorkDir(File d) { 104 this.workDir = d.getAbsoluteFile().toPath().normalize(); 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 @Override 140 public Iterable<InputFile> inputFiles(FilePredicate predicate) { 141 doPreloadFiles(); 142 return OptimizedFilePredicateAdapter.create(predicate).get(cache); 143 } 144 145 @Override 146 public boolean hasFiles(FilePredicate predicate) { 147 return inputFiles(predicate).iterator().hasNext(); 148 } 149 150 @Override 151 public Iterable<File> files(FilePredicate predicate) { 152 doPreloadFiles(); 153 return Iterables.transform(inputFiles(predicate), new Function<InputFile, File>() { 154 @Override 155 public File apply(InputFile input) { 156 return input.file(); 157 } 158 }); 159 } 160 161 @Override 162 public InputDir inputDir(File dir) { 163 doPreloadFiles(); 164 String relativePath = PathUtils.sanitize(new PathResolver().relativePath(baseDir.toFile(), dir)); 165 if (relativePath == null) { 166 return null; 167 } 168 return cache.inputDir(relativePath); 169 } 170 171 /** 172 * Adds InputFile to the list and registers its language, if present. 173 * Synchronized because PersistIt Exchange is not concurrent 174 */ 175 public synchronized DefaultFileSystem add(DefaultInputFile inputFile) { 176 if (this.baseDir == null) { 177 throw new IllegalStateException("Please set basedir on filesystem before adding files"); 178 } 179 inputFile.setModuleBaseDir(this.baseDir); 180 cache.add(inputFile); 181 String language = inputFile.language(); 182 if (language != null) { 183 languages.add(language); 184 } 185 return this; 186 } 187 188 /** 189 * Adds InputDir to the list. 190 * Synchronized because PersistIt Exchange is not concurrent 191 */ 192 public synchronized DefaultFileSystem add(DefaultInputDir inputDir) { 193 if (this.baseDir == null) { 194 throw new IllegalStateException("Please set basedir on filesystem before adding dirs"); 195 } 196 inputDir.setModuleBaseDir(this.baseDir); 197 cache.add(inputDir); 198 return this; 199 } 200 201 /** 202 * Adds a language to the list. To be used only for unit tests that need to use {@link #languages()} without 203 * using {@link #add(org.sonar.api.batch.fs.InputFile)}. 204 */ 205 public DefaultFileSystem addLanguages(String language, String... others) { 206 languages.add(language); 207 Collections.addAll(languages, others); 208 return this; 209 } 210 211 @Override 212 public SortedSet<String> languages() { 213 doPreloadFiles(); 214 return languages; 215 } 216 217 @Override 218 public FilePredicates predicates() { 219 return predicates; 220 } 221 222 /** 223 * This method is called before each search of files. 224 */ 225 protected void doPreloadFiles() { 226 // nothing to do by default 227 } 228 229 public abstract static class Cache implements Index { 230 @Override 231 public abstract Iterable<InputFile> inputFiles(); 232 233 @Override 234 @CheckForNull 235 public abstract InputFile inputFile(String relativePath); 236 237 @Override 238 @CheckForNull 239 public abstract InputDir inputDir(String relativePath); 240 241 protected abstract void doAdd(InputFile inputFile); 242 243 protected abstract void doAdd(InputDir inputDir); 244 245 final void add(InputFile inputFile) { 246 doAdd(inputFile); 247 } 248 249 public void add(InputDir inputDir) { 250 doAdd(inputDir); 251 } 252 253 } 254 255 /** 256 * Used only for testing 257 */ 258 private static class MapCache extends Cache { 259 private final Map<String, InputFile> fileMap = new HashMap<String, InputFile>(); 260 private final Map<String, InputDir> dirMap = new HashMap<String, InputDir>(); 261 262 @Override 263 public Iterable<InputFile> inputFiles() { 264 return new ArrayList<InputFile>(fileMap.values()); 265 } 266 267 @Override 268 public InputFile inputFile(String relativePath) { 269 return fileMap.get(relativePath); 270 } 271 272 @Override 273 public InputDir inputDir(String relativePath) { 274 return dirMap.get(relativePath); 275 } 276 277 @Override 278 protected void doAdd(InputFile inputFile) { 279 fileMap.put(inputFile.relativePath(), inputFile); 280 } 281 282 @Override 283 protected void doAdd(InputDir inputDir) { 284 dirMap.put(inputDir.relativePath(), inputDir); 285 } 286 } 287 288 @Override 289 public File resolvePath(String path) { 290 File file = new File(path); 291 if (!file.isAbsolute()) { 292 try { 293 file = new File(baseDir(), path).getCanonicalFile(); 294 } catch (IOException e) { 295 throw new IllegalArgumentException("Unable to resolve path '" + path + "'", e); 296 } 297 } 298 return file; 299 } 300}