Home | History | Annotate | Download | only in tools
      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 
     53 	public FileProcessor () {
     54 	}
     55 
     56 	/** Copy constructor. */
     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 
     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. */
     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. */
     78 	public FileProcessor addInputSuffix (String... suffixes) {
     79 		for (String suffix : suffixes)
     80 			addInputRegex("(?i).*" + Pattern.quote(suffix));
     81 		return this;
     82 	}
     83 
     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. */
     91 	public FileProcessor setOutputSuffix (String outputSuffix) {
     92 		this.outputSuffix = outputSuffix;
     93 		return this;
     94 	}
     95 
     96 	public FileProcessor setFlattenOutput (boolean flattenOutput) {
     97 		this.flattenOutput = flattenOutput;
     98 		return this;
     99 	}
    100 
    101 	/** Default is true. */
    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) */
    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)}. */
    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)}. */
    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 
    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. */
    226 	protected void processFile (Entry entry) throws Exception {
    227 	}
    228 
    229 	/** Called for each input directory. The files will be {@link #setComparator(Comparator) sorted}. */
    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. */
    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 
    247 		public Entry () {
    248 		}
    249 
    250 		public Entry (File inputFile, File outputFile) {
    251 			this.inputFile = inputFile;
    252 			this.outputFile = outputFile;
    253 		}
    254 
    255 		public String toString () {
    256 			return inputFile.toString();
    257 		}
    258 	}
    259 }
    260