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