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