/******************************************************************************* * Copyright 2015 Maximilian Stark | Dakror <mail@dakror.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package de.dakror.vloxlands.util; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Comparator; import java.util.Locale; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.utils.Array; /** * This class creates a list of all internal assets files * to allow iterating through internal directories.<br> * To start the class call {@link #init()} in the startup of your program. * * @author Dakror */ public class InternalAssetManager { /** * Kindly copied from javax.swing.filechooser with removal of descriptions <br> * <br> * An implementation of {@link FileFilter} that filters using a * specified set of extensions. The extension for a file is the * portion of the file name after the last ".". Files whose name does * not contain a "." have no file name extension. File name extension * comparisons are case insensitive. * <p> * The following example creates a {@code FileNameExtensionFilter} that will show {@code jpg} files: * * <pre> * FileFilter filter = new FileNameExtensionFilter("jpg", "jpeg"); * </pre> * * @see javax.swing.filechooser.FileNameExtensionFilter * @see FileFilter * @see javax.swing.JFileChooser#setFileFilter * @see javax.swing.JFileChooser#addChoosableFileFilter * @see javax.swing.JFileChooser#getFileFilter */ public static class FileNameExtensionFilter implements FileFilter { // Known extensions. private final String[] extensions; // Cached ext private final String[] lowerCaseExtensions; /** * Creates a {@code FileNameExtensionFilter} with the specified * description and file name extensions. The returned {@code FileNameExtensionFilter} will accept all directories and any * file with a file name extension contained in {@code extensions}. * * @param extensions the accepted file name extensions * @throws IllegalArgumentException if extensions is {@code null}, empty, * contains {@code null}, or contains an empty string * @see #accept */ public FileNameExtensionFilter(String... extensions) { if (extensions == null || extensions.length == 0) { throw new IllegalArgumentException("Extensions must be non-null and not empty"); } this.extensions = new String[extensions.length]; lowerCaseExtensions = new String[extensions.length]; for (int i = 0; i < extensions.length; i++) { if (extensions[i] == null || extensions[i].length() == 0) { throw new IllegalArgumentException("Each extension must be non-null and not empty"); } this.extensions[i] = extensions[i]; lowerCaseExtensions[i] = extensions[i].toLowerCase(Locale.ENGLISH); } } /** * Tests the specified file, returning true if the file is * accepted, false otherwise. True is returned if the extension * matches one of the file name extensions of this {@code FileFilter}, or * the file is a directory. * * @param f the {@code File} to test * @return true if the file is to be accepted, false otherwise */ @Override public boolean accept(File f) { if (f != null) { if (f.isDirectory()) { return true; } // NOTE: we tested implementations using Maps, binary search // on a sorted list and this implementation. All implementations // provided roughly the same speed, most likely because of // overhead associated with java.io.File. Therefor we've stuck // with the simple lightweight approach. String fileName = f.getName(); int i = fileName.lastIndexOf('.'); if (i > 0 && i < fileName.length() - 1) { String desiredExtension = fileName.substring(i + 1).toLowerCase(Locale.ENGLISH); for (String extension : lowerCaseExtensions) { if (desiredExtension.equals(extension)) { return true; } } } } return false; } /** * Returns the set of file name extensions files are tested against. * * @return the set of file name extensions files are tested against */ public String[] getExtensions() { String[] result = new String[extensions.length]; System.arraycopy(extensions, 0, result, 0, extensions.length); return result; } /** * Returns a string representation of the {@code FileNameExtensionFilter}. * This method is intended to be used for debugging purposes, * and the content and format of the returned string may vary * between implementations. * * @return a string representation of this {@code FileNameExtensionFilter} */ @Override public String toString() { return super.toString() + "[extensions=" + java.util.Arrays.asList(getExtensions()) + "]"; } } public static class FileNode { public FileNode parent; public FileHandle file; public Array<FileNode> children; public boolean directory; public FileNode(FileNode parent, FileHandle file, boolean directory) { this.parent = parent; this.file = file; this.directory = directory; children = new Array<FileNode>(); } @Override public String toString() { return (parent != null && parent.file != null ? parent.file.name() : "") + ", " + (file != null ? file.name() : "") + ", " + children; } } static FileNode root; /** * Initializes the InternalAssetManager.<br> * Checks wether the program is currently running from a jar file.<br> * If not, a list <i>FILES.txt</i> gets created containing all files and * directories inside the assets folder.<br> * Then all files and directories get loaded in a tree data structure. */ public static void init() { try { Reader reader = null; if (!isRunningFromJarFile()) { File parent = new File(InternalAssetManager.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getParentFile(); File dst = new File(parent, "core/assets"); if (!dst.exists()) dst = new File(parent, "android/assets"); if (!dst.exists()) throw new FileNotFoundException("Could not locate assets folder"); StringBuffer sb = new StringBuffer(); writeDirectory(sb, dst, dst.getPath().length() + 1); FileWriter fw = new FileWriter(new File(dst, "FILES.txt")); String s = sb.toString(); fw.write(s); fw.close(); reader = new StringReader(s); } int files = 0; FileHandle fh = Gdx.files.internal("FILES.txt"); BufferedReader br = new BufferedReader(reader == null ? fh.reader() : reader); String line = ""; root = new FileNode(null, null, true); FileNode activeNode = root; int slashes = -1; while ((line = br.readLine()) != null) { boolean dir = line.startsWith("d"); String path = line.substring(2); int sl = path.split("/").length - 1; if (slashes == -1) slashes = sl; while (slashes > sl) { activeNode = activeNode.parent; slashes--; } if (slashes == sl) { if (dir) { FileNode node = new FileNode(activeNode, Gdx.files.internal(path), true); activeNode.children.add(node); activeNode = node; slashes++; } else { FileNode node = new FileNode(activeNode, Gdx.files.internal(path), false); activeNode.children.add(node); files++; } } } Gdx.app.log("InternalAssetsManager.init", files + " files loaded."); } catch (Exception e) { e.printStackTrace(); } } /** * @param path the internal directory * @return a list of all FILES in the specified directory */ public static FileNode[] listFiles(String path) { return listFiles(path, false); } /** * @param path the internal directory * @param recursive wether all files in all subfolders should be listed too * @return a list of all FILES in the specified directory */ public static FileNode[] listFiles(String path, boolean recursive) { return list(path, true, false, recursive); } /** * @param path the internal directory * @return a list of all DIRECTORIES in the specified directory */ public static FileNode[] listDirectories(String path) { return listDirectories(path, false); } /** * @param path the internal directory * @param recursive wether all files in all subfolders should be listed too * @return a list of all DIRECTORIES in the specified directory */ public static FileNode[] listDirectories(String path, boolean recursive) { return list(path, false, true, recursive); } /** * @param path the internal directory * @return a list of EVERYTHING in the specified directory */ public static FileNode[] list(String path) { return list(path, false); } /** * @param path the internal directory * @param recursive wether all files in all subfolders should be listed too * @return a list of EVERYTHING in the specified directory */ public static FileNode[] list(String path, boolean recursive) { return list(path, true, true, recursive); } /** * @return wether the current program is located in a file tree or packed jar * archive */ public static boolean isRunningFromJarFile() { try { return new File(InternalAssetManager.class.getProtectionDomain().getCodeSource().getLocation().toURI()).isFile(); } catch (URISyntaxException e) { e.printStackTrace(); return false; } } /** * Calls {@link AssetManager#load(String, Class)} for all files found in the * directory.<br> * If <code>recursive</code> is defined all files in all subfolders will * get scheduled for loading as well. * * @param assets the AssetManager to load the found assets * @param path the directory to be loaded * @param type the AssetManager Type e.g {@link Texture} * @param recursive wether all files in all subfolders should be loaded too */ public static void scheduleDirectory(AssetManager assets, String path, Class<?> type, boolean recursive) { scheduleDirectory(assets, path, type, new FileFilter() { @Override public boolean accept(File pathname) { return true; } }, recursive); } /** * Calls {@link AssetManager#load(String, Class)} for all files found in the * directory.<br> * If <code>recursive</code> is defined all files in all subfolders will * get scheduled for loading as well. * * @param assets the AssetManager to load the found assets * @param path the directory to be loaded * @param type the AssetManager Type e.g {@link Texture} * @param fileFilter the {@link FileFilter} to apply. Use e.g a {@link FileNameExtensionFilter} * @param recursive wether all files in all subfolders should be loaded too */ public static void scheduleDirectory(AssetManager assets, String path, Class<?> type, FileFilter fileFilter, boolean recursive) { for (FileNode fn : listFiles(path, recursive)) { if (fileFilter.accept(fn.file.file())) assets.load(fn.file.path(), type); } } static FileNode[] list(String path, boolean f, boolean d, boolean recursive) { FileHandle dir = Gdx.files.internal(path); FileNode fn = traverseTo(dir, root); Array<FileNode> files = new Array<FileNode>(); Array<FileNode> dirs = new Array<FileNode>(); for (FileNode file : fn.children) { if (file.directory) { if (d) files.add(file); if (recursive) dirs.add(file); } if (!file.directory && f) files.add(file); } if (recursive) { for (FileNode file : dirs) files.addAll(list(file.file.path(), f, d, true)); } return files.toArray(FileNode.class); } static FileNode traverseTo(FileHandle searched, FileNode parent) { if (parent.file != null && searched.equals(parent.file)) return parent; for (FileNode fn : parent.children) { if (fn.directory && searched.path().substring(0, Math.min(fn.file.path().length(), searched.path().length())).equals(fn.file.path())) { return traverseTo(searched, fn); } } return null; } static void writeDirectory(StringBuffer sb, File dir, int pathOffset) throws IOException { File[] files = dir.listFiles(); Arrays.sort(files, new Comparator<File>() { @Override public int compare(File o1, File o2) { boolean d1 = o1.isDirectory(); boolean d2 = o2.isDirectory(); if (d2 && !d1) return -1; else if (d1 && !d2) return 1; else return o1.getName().compareTo(o2.getName()); } }); for (File f : files) { sb.append((f.isDirectory() ? "d " : "f ") + f.getPath().substring(pathOffset).replace("\\", "/") + "\r\n"); if (f.isDirectory()) writeDirectory(sb, f, pathOffset); } } }