001/* 002 * SonarQube, open source software quality management tool. 003 * Copyright (C) 2008-2013 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.base.Preconditions; 024import com.google.common.base.Predicate; 025import com.google.common.collect.Iterables; 026import com.google.common.collect.Lists; 027import com.google.common.collect.Maps; 028import com.google.common.collect.Sets; 029import org.sonar.api.batch.fs.FilePredicate; 030import org.sonar.api.batch.fs.FileSystem; 031import org.sonar.api.batch.fs.InputFile; 032import org.sonar.api.batch.fs.UniqueIndexPredicate; 033 034import javax.annotation.CheckForNull; 035import javax.annotation.Nullable; 036import java.io.File; 037import java.nio.charset.Charset; 038import java.util.List; 039import java.util.Map; 040import java.util.NoSuchElementException; 041import java.util.SortedSet; 042 043/** 044 * @since 4.2 045 */ 046public class DefaultFileSystem implements FileSystem { 047 048 private final Cache cache; 049 private final SortedSet<String> languages = Sets.newTreeSet(); 050 private File baseDir, workDir; 051 private Charset encoding; 052 053 /** 054 * Only for testing 055 */ 056 public DefaultFileSystem() { 057 this.cache = new MapCache(); 058 } 059 060 protected DefaultFileSystem(Cache cache) { 061 this.cache = cache; 062 } 063 064 public DefaultFileSystem setBaseDir(File d) { 065 Preconditions.checkNotNull(d, "Base directory can't be null"); 066 this.baseDir = d.getAbsoluteFile(); 067 return this; 068 } 069 070 @Override 071 public File baseDir() { 072 return baseDir; 073 } 074 075 public DefaultFileSystem setEncoding(@Nullable Charset e) { 076 this.encoding = e; 077 return this; 078 } 079 080 @Override 081 public Charset encoding() { 082 return encoding == null ? Charset.defaultCharset() : encoding; 083 } 084 085 public boolean isDefaultJvmEncoding() { 086 return encoding == null; 087 } 088 089 public DefaultFileSystem setWorkDir(File d) { 090 this.workDir = d.getAbsoluteFile(); 091 return this; 092 } 093 094 @Override 095 public File workDir() { 096 return workDir; 097 } 098 099 @Override 100 public InputFile inputFile(FilePredicate predicate) { 101 doPreloadFiles(); 102 if (predicate instanceof UniqueIndexPredicate) { 103 return cache.inputFile((UniqueIndexPredicate) predicate); 104 } 105 try { 106 Iterable<InputFile> files = inputFiles(predicate); 107 return Iterables.getOnlyElement(files); 108 } catch (NoSuchElementException e) { 109 // contrary to guava, return null if iterable is empty 110 return null; 111 } 112 } 113 114 @Override 115 public Iterable<InputFile> inputFiles(FilePredicate predicate) { 116 doPreloadFiles(); 117 return Iterables.filter(cache.inputFiles(), new GuavaPredicate(predicate)); 118 } 119 120 @Override 121 public boolean hasFiles(FilePredicate predicate) { 122 doPreloadFiles(); 123 return Iterables.indexOf(cache.inputFiles(), new GuavaPredicate(predicate)) >= 0; 124 } 125 126 @Override 127 public Iterable<File> files(FilePredicate predicate) { 128 doPreloadFiles(); 129 return Iterables.transform(inputFiles(predicate), new Function<InputFile, File>() { 130 @Override 131 public File apply(@Nullable InputFile input) { 132 return input == null ? null : input.file(); 133 } 134 }); 135 } 136 137 /** 138 * Adds InputFile to the list and registers its language, if present. 139 */ 140 public DefaultFileSystem add(InputFile inputFile) { 141 cache.add(inputFile); 142 if (inputFile.language() != null) { 143 languages.add(inputFile.language()); 144 } 145 return this; 146 } 147 148 /** 149 * Adds a language to the list. To be used only for unit tests that need to use {@link #languages()} without 150 * using {@link #add(org.sonar.api.batch.fs.InputFile)}. 151 */ 152 public DefaultFileSystem addLanguages(String language, String... others) { 153 languages.add(language); 154 for (String other : others) { 155 languages.add(other); 156 } 157 return this; 158 } 159 160 @Override 161 public SortedSet<String> languages() { 162 doPreloadFiles(); 163 return languages; 164 } 165 166 /** 167 * This method is called before each search of files. 168 */ 169 protected void doPreloadFiles() { 170 // nothing to do by default 171 } 172 173 public static abstract class Cache { 174 protected abstract Iterable<InputFile> inputFiles(); 175 176 @CheckForNull 177 protected abstract InputFile inputFile(UniqueIndexPredicate predicate); 178 179 protected abstract void doAdd(InputFile inputFile); 180 181 protected abstract void doIndex(String indexId, Object value, InputFile inputFile); 182 183 final void add(InputFile inputFile) { 184 doAdd(inputFile); 185 for (FileIndex index : FileIndex.ALL) { 186 doIndex(index.id(), index.valueOf(inputFile), inputFile); 187 } 188 } 189 } 190 191 /** 192 * Used only for testing 193 */ 194 private static class MapCache extends Cache { 195 private final List<InputFile> files = Lists.newArrayList(); 196 private final Map<String, Map<Object, InputFile>> fileMap = Maps.newHashMap(); 197 198 @Override 199 public Iterable<InputFile> inputFiles() { 200 return Lists.newArrayList(files); 201 } 202 203 @Override 204 public InputFile inputFile(UniqueIndexPredicate predicate) { 205 Map<Object, InputFile> byAttr = fileMap.get(predicate.indexId()); 206 if (byAttr != null) { 207 return byAttr.get(predicate.value()); 208 } 209 return null; 210 } 211 212 @Override 213 protected void doAdd(InputFile inputFile) { 214 files.add(inputFile); 215 } 216 217 @Override 218 protected void doIndex(String indexId, Object value, InputFile inputFile) { 219 Map<Object, InputFile> attrValues = fileMap.get(indexId); 220 if (attrValues == null) { 221 attrValues = Maps.newHashMap(); 222 fileMap.put(indexId, attrValues); 223 } 224 attrValues.put(value, inputFile); 225 } 226 } 227 228 private static class GuavaPredicate implements Predicate<InputFile> { 229 private final FilePredicate predicate; 230 231 private GuavaPredicate(FilePredicate predicate) { 232 this.predicate = predicate; 233 } 234 235 @Override 236 public boolean apply(@Nullable InputFile input) { 237 return input != null && predicate.apply(input); 238 } 239 } 240}