1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.tools; 18 19 import com.badlogic.gdx.utils.Array; 20 21 import java.io.File; 22 import java.io.FilenameFilter; 23 import java.util.ArrayList; 24 import java.util.Collections; 25 import java.util.Comparator; 26 import java.util.LinkedHashMap; 27 import java.util.regex.Pattern; 28 29 /** Collects files recursively, filtering by file name. Callbacks are provided to process files and the results are collected, 30 * either {@link #processFile(Entry)} or {@link #processDir(Entry, ArrayList)} can be overridden, or both. The entries provided to 31 * the callbacks have the original file, the output directory, and the output file. If {@link #setFlattenOutput(boolean)} is 32 * false, the output will match the directory structure of the input. 33 * @author Nathan Sweet */ 34 public class FileProcessor { 35 FilenameFilter inputFilter; 36 Comparator<File> comparator = new Comparator<File>() { 37 public int compare (File o1, File o2) { 38 return o1.getName().compareTo(o2.getName()); 39 } 40 }; 41 Array<Pattern> inputRegex = new Array(); 42 String outputSuffix; 43 ArrayList<Entry> outputFiles = new ArrayList(); 44 boolean recursive = true; 45 boolean flattenOutput; 46 47 Comparator<Entry> entryComparator = new Comparator<Entry>() { 48 public int compare (Entry o1, Entry o2) { 49 return comparator.compare(o1.inputFile, o2.inputFile); 50 } 51 }; 52 FileProcessor()53 public FileProcessor () { 54 } 55 56 /** Copy constructor. */ FileProcessor(FileProcessor processor)57 public FileProcessor (FileProcessor processor) { 58 inputFilter = processor.inputFilter; 59 comparator = processor.comparator; 60 inputRegex.addAll(processor.inputRegex); 61 outputSuffix = processor.outputSuffix; 62 recursive = processor.recursive; 63 flattenOutput = processor.flattenOutput; 64 } 65 setInputFilter(FilenameFilter inputFilter)66 public FileProcessor setInputFilter (FilenameFilter inputFilter) { 67 this.inputFilter = inputFilter; 68 return this; 69 } 70 71 /** Sets the comparator for {@link #processDir(Entry, ArrayList)}. By default the files are sorted by alpha. */ setComparator(Comparator<File> comparator)72 public FileProcessor setComparator (Comparator<File> comparator) { 73 this.comparator = comparator; 74 return this; 75 } 76 77 /** Adds a case insensitive suffix for matching input files. */ addInputSuffix(String... suffixes)78 public FileProcessor addInputSuffix (String... suffixes) { 79 for (String suffix : suffixes) 80 addInputRegex("(?i).*" + Pattern.quote(suffix)); 81 return this; 82 } 83 addInputRegex(String... regexes)84 public FileProcessor addInputRegex (String... regexes) { 85 for (String regex : regexes) 86 inputRegex.add(Pattern.compile(regex)); 87 return this; 88 } 89 90 /** Sets the suffix for output files, replacing the extension of the input file. */ setOutputSuffix(String outputSuffix)91 public FileProcessor setOutputSuffix (String outputSuffix) { 92 this.outputSuffix = outputSuffix; 93 return this; 94 } 95 setFlattenOutput(boolean flattenOutput)96 public FileProcessor setFlattenOutput (boolean flattenOutput) { 97 this.flattenOutput = flattenOutput; 98 return this; 99 } 100 101 /** Default is true. */ setRecursive(boolean recursive)102 public FileProcessor setRecursive (boolean recursive) { 103 this.recursive = recursive; 104 return this; 105 } 106 107 /** @param outputRoot May be null. 108 * @see #process(File, File) */ process(String inputFileOrDir, String outputRoot)109 public ArrayList<Entry> process (String inputFileOrDir, String outputRoot) throws Exception { 110 return process(new File(inputFileOrDir), outputRoot == null ? null : new File(outputRoot)); 111 } 112 113 /** Processes the specified input file or directory. 114 * @param outputRoot May be null if there is no output from processing the files. 115 * @return the processed files added with {@link #addProcessedFile(Entry)}. */ process(File inputFileOrDir, File outputRoot)116 public ArrayList<Entry> process (File inputFileOrDir, File outputRoot) throws Exception { 117 if (!inputFileOrDir.exists()) throw new IllegalArgumentException("Input file does not exist: " + inputFileOrDir.getAbsolutePath()); 118 if (inputFileOrDir.isFile()) 119 return process(new File[] {inputFileOrDir}, outputRoot); 120 else 121 return process(inputFileOrDir.listFiles(), outputRoot); 122 } 123 124 /** Processes the specified input files. 125 * @param outputRoot May be null if there is no output from processing the files. 126 * @return the processed files added with {@link #addProcessedFile(Entry)}. */ process(File[] files, File outputRoot)127 public ArrayList<Entry> process (File[] files, File outputRoot) throws Exception { 128 if (outputRoot == null) outputRoot = new File(""); 129 outputFiles.clear(); 130 131 LinkedHashMap<File, ArrayList<Entry>> dirToEntries = new LinkedHashMap(); 132 process(files, outputRoot, outputRoot, dirToEntries, 0); 133 134 ArrayList<Entry> allEntries = new ArrayList(); 135 for (java.util.Map.Entry<File, ArrayList<Entry>> mapEntry : dirToEntries.entrySet()) { 136 ArrayList<Entry> dirEntries = mapEntry.getValue(); 137 if (comparator != null) Collections.sort(dirEntries, entryComparator); 138 139 File inputDir = mapEntry.getKey(); 140 File newOutputDir = null; 141 if (flattenOutput) 142 newOutputDir = outputRoot; 143 else if (!dirEntries.isEmpty()) // 144 newOutputDir = dirEntries.get(0).outputDir; 145 String outputName = inputDir.getName(); 146 if (outputSuffix != null) outputName = outputName.replaceAll("(.*)\\..*", "$1") + outputSuffix; 147 148 Entry entry = new Entry(); 149 entry.inputFile = mapEntry.getKey(); 150 entry.outputDir = newOutputDir; 151 if (newOutputDir != null) 152 entry.outputFile = newOutputDir.length() == 0 ? new File(outputName) : new File(newOutputDir, outputName); 153 154 try { 155 processDir(entry, dirEntries); 156 } catch (Exception ex) { 157 throw new Exception("Error processing directory: " + entry.inputFile.getAbsolutePath(), ex); 158 } 159 allEntries.addAll(dirEntries); 160 } 161 162 if (comparator != null) Collections.sort(allEntries, entryComparator); 163 for (Entry entry : allEntries) { 164 try { 165 processFile(entry); 166 } catch (Exception ex) { 167 throw new Exception("Error processing file: " + entry.inputFile.getAbsolutePath(), ex); 168 } 169 } 170 171 return outputFiles; 172 } 173 process(File[] files, File outputRoot, File outputDir, LinkedHashMap<File, ArrayList<Entry>> dirToEntries, int depth)174 private void process (File[] files, File outputRoot, File outputDir, LinkedHashMap<File, ArrayList<Entry>> dirToEntries, 175 int depth) { 176 // Store empty entries for every directory. 177 for (File file : files) { 178 File dir = file.getParentFile(); 179 ArrayList<Entry> entries = dirToEntries.get(dir); 180 if (entries == null) { 181 entries = new ArrayList(); 182 dirToEntries.put(dir, entries); 183 } 184 } 185 186 for (File file : files) { 187 if (file.isFile()) { 188 if (inputRegex.size > 0) { 189 boolean found = false; 190 for (Pattern pattern : inputRegex) { 191 if (pattern.matcher(file.getName()).matches()) { 192 found = true; 193 continue; 194 } 195 } 196 if (!found) continue; 197 } 198 199 File dir = file.getParentFile(); 200 if (inputFilter != null && !inputFilter.accept(dir, file.getName())) continue; 201 202 String outputName = file.getName(); 203 if (outputSuffix != null) outputName = outputName.replaceAll("(.*)\\..*", "$1") + outputSuffix; 204 205 Entry entry = new Entry(); 206 entry.depth = depth; 207 entry.inputFile = file; 208 entry.outputDir = outputDir; 209 210 if (flattenOutput) { 211 entry.outputFile = new File(outputRoot, outputName); 212 } else { 213 entry.outputFile = new File(outputDir, outputName); 214 } 215 216 dirToEntries.get(dir).add(entry); 217 } 218 if (recursive && file.isDirectory()) { 219 File subdir = outputDir.getPath().length() == 0 ? new File(file.getName()) : new File(outputDir, file.getName()); 220 process(file.listFiles(inputFilter), outputRoot, subdir, dirToEntries, depth + 1); 221 } 222 } 223 } 224 225 /** Called with each input file. */ processFile(Entry entry)226 protected void processFile (Entry entry) throws Exception { 227 } 228 229 /** Called for each input directory. The files will be {@link #setComparator(Comparator) sorted}. */ processDir(Entry entryDir, ArrayList<Entry> files)230 protected void processDir (Entry entryDir, ArrayList<Entry> files) throws Exception { 231 } 232 233 /** This method should be called by {@link #processFile(Entry)} or {@link #processDir(Entry, ArrayList)} if the return value of 234 * {@link #process(File, File)} or {@link #process(File[], File)} should return all the processed files. */ addProcessedFile(Entry entry)235 protected void addProcessedFile (Entry entry) { 236 outputFiles.add(entry); 237 } 238 239 /** @author Nathan Sweet */ 240 static public class Entry { 241 public File inputFile; 242 /** May be null. */ 243 public File outputDir; 244 public File outputFile; 245 public int depth; 246 Entry()247 public Entry () { 248 } 249 Entry(File inputFile, File outputFile)250 public Entry (File inputFile, File outputFile) { 251 this.inputFile = inputFile; 252 this.outputFile = outputFile; 253 } 254 toString()255 public String toString () { 256 return inputFile.toString(); 257 } 258 } 259 } 260