// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.resource.key; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import org.infinity.util.Misc; public final class ResourceTreeModel implements TreeModel { private final List<TreeModelListener> treeModelListeners = new ArrayList<>(); private final Map<String, ResourceEntry> entries = new HashMap<>(25000); private final Map<String, ResourceTreeFolder> folders = new TreeMap<>(Misc.getIgnoreCaseComparator()); private final ResourceTreeFolder root = new ResourceTreeFolder(null, ""); public ResourceTreeModel() { } // --------------------- Begin Interface TreeModel --------------------- @Override public Object getRoot() { return root; } @Override public Object getChild(Object parent, int index) { if (parent instanceof ResourceTreeFolder) { return ((ResourceTreeFolder)parent).getChild(index); } return null; } @Override public int getChildCount(Object parent) { if (parent instanceof ResourceTreeFolder) { return ((ResourceTreeFolder)parent).getChildCount(); } return 0; } @Override public boolean isLeaf(Object node) { return !(node instanceof ResourceTreeFolder); } @Override public void valueForPathChanged(TreePath path, Object newvalue) { throw new IllegalArgumentException(); // Not allowed } @Override public int getIndexOfChild(Object parent, Object child) { if (parent instanceof ResourceTreeFolder) { return ((ResourceTreeFolder)parent).getIndexOfChild(child); } return -1; } @Override public void addTreeModelListener(TreeModelListener l) { treeModelListeners.add(l); } @Override public void removeTreeModelListener(TreeModelListener l) { treeModelListeners.remove(l); } // --------------------- End Interface TreeModel --------------------- public void addDirectory(ResourceTreeFolder parentFolder, Path directory, boolean overwrite) { try (DirectoryStream<Path> dstream = Files.newDirectoryStream(directory)) { Iterator<Path> iter = dstream.iterator(); if (iter.hasNext()) { final ResourceTreeFolder folder = addFolder(parentFolder, directory.getFileName().toString()); iter.forEachRemaining((path) -> { if (Files.isDirectory(path)) { addDirectory(folder, path, overwrite); } else { folder.addResourceEntry(new FileResourceEntry(path), overwrite); } }); parentFolder.sortChildren(true); // TreePath path = getPathToNode(parentFolder); // TreeModelEvent event = new TreeModelEvent(this, path, // new int[]{getIndexOfChild(parentFolder, folder)}, // new Object[]{folder}); // for (int i = 0; i < treeModelListeners.size(); i++) { // treeModelListeners.get(i).treeNodesInserted(event); // } } } catch (IOException e) { e.printStackTrace(); return; } } public ResourceTreeFolder addResourceEntry(ResourceEntry entry, String folderName, boolean overwrite) { ResourceTreeFolder folder = addFolder(folderName); folder.addResourceEntry(entry, overwrite); if (entry.isVisible()) { entries.put(entry.getResourceName().toUpperCase(Locale.ENGLISH), entry); folder.sortChildren(false); // TreePath path = getPathToNode(entry).getParentPath(); // TreeModelEvent event = new TreeModelEvent(this, path, // new int[]{getIndexOfChild(folder, entry)}, // new Object[]{entry}); // for (int i = 0; i < treeModelListeners.size(); i++) { // treeModelListeners.get(i).treeNodesInserted(event); // } } return folder; } public List<BIFFResourceEntry> getBIFFResourceEntries() { return getBIFFResourceEntries(null); } public List<BIFFResourceEntry> getBIFFResourceEntries(Path keyFile) { List<BIFFResourceEntry> list = new ArrayList<BIFFResourceEntry>(); for (int i = 0; i < root.getFolders().size(); i++) { List<ResourceEntry> entries = root.getFolders().get(i).getResourceEntries(); for (int j = 0; j < entries.size(); j++) { ResourceEntry o = entries.get(j); if (o instanceof BIFFResourceEntry) { BIFFResourceEntry bre = (BIFFResourceEntry)o; if (keyFile == null || bre.getKeyfile().equals(keyFile)) { list.add((BIFFResourceEntry)o); } } } } return list; } public ResourceTreeFolder getFolder(ResourceTreeFolder parentFolder, String name) { ResourceTreeFolder folder = null; if (parentFolder != null) { for (final ResourceTreeFolder rtf: parentFolder.getFolders()) { if (rtf.folderName().equalsIgnoreCase(name)) { folder = rtf; break; } } } return folder; } public ResourceTreeFolder getFolder(String text) { return folders.get(text); } /** * Adds a folder of specified name to the root folder if not yet existing. * Returns the new or existing folder */ public ResourceTreeFolder addFolder(String folderName) { return addFolder(root, folderName); } /** * Adds a folder of specified name to the parent folder if not yet existing. * Returns the new or existing folder */ public ResourceTreeFolder addFolder(ResourceTreeFolder parent, String folderName) { if (folderName != null) { if (parent == null) { parent = root; } ResourceTreeFolder folder = getFolder(parent, folderName); if (folder == null) { if (folderName.length() > 0) { folderName = Character.toUpperCase(folderName.charAt(0)) + folderName.substring(1); } folder = new ResourceTreeFolder(parent, folderName); folders.put(folderName, folder); parent.addFolder(folder); parent.sortChildren(false); // TreePath path = getPathToNode(parent); // TreeModelEvent event = new TreeModelEvent(this, path, // new int[]{getIndexOfChild(parent, folder)}, // new Object[]{folder}); // for (int i = 0; i < treeModelListeners.size(); i++) { // treeModelListeners.get(i).treeNodesInserted(event); // } } return folder; } return null; } public TreePath getPathToNode(ResourceEntry entry) { List<Object> path = new ArrayList<>(4); path.add(entry); ResourceTreeFolder parent = folders.get(entry.getTreeFolder()); while (parent != null) { path.add(parent); parent = parent.getParentFolder(); } Collections.reverse(path); return new TreePath(path.toArray()); } public TreePath getPathToNode(ResourceTreeFolder folder) { TreePath retVal = null; if (folder != null) { List<Object> path = new ArrayList<>(4); while (folder != null) { path.add(folder); folder = folder.getParentFolder(); } retVal = new TreePath(path.toArray()); } return retVal; } public Collection<ResourceEntry> getResourceEntries() { return entries.values(); } public ResourceEntry getResourceEntry(String entryname) { if (entryname != null) { return entries.get(entryname.toUpperCase(Locale.ENGLISH)); } else { return null; } } public List<ResourceEntry> removeDirectory(ResourceTreeFolder parentFolder, String folderName) { List<ResourceEntry> retVal = new ArrayList<>(); if (folderName != null) { if (parentFolder == null) { parentFolder = root; } ResourceTreeFolder folder = getFolder(parentFolder, folderName); if (folder != null) { List<ResourceEntry> entries = folder.getResourceEntries(); for (final ResourceEntry entry: entries) { folder.removeResourceEntry(entry); } parentFolder.removeFolder(folder); folders.remove(folder.folderName()); retVal.addAll(entries); } } return retVal; } public void removeResourceEntry(ResourceEntry entry) { removeResourceEntry(entry, entry.getTreeFolder()); } public void removeResourceEntry(ResourceEntry entry, String folder) { ResourceTreeFolder parent = folders.get(folder); if (parent == null) { return; } TreePath path = getPathToNode(entry).getParentPath(); TreeModelEvent event = new TreeModelEvent(this, path, new int[]{getIndexOfChild(parent, entry)}, new Object[]{entry}); parent.removeResourceEntry(entry); entries.remove(entry.toString().toUpperCase(Locale.ENGLISH)); if (parent.getChildCount() == 0) { root.removeFolder(parent); folders.remove(parent.folderName()); } for (int i = 0; i < treeModelListeners.size(); i++) { treeModelListeners.get(i).treeNodesRemoved(event); } } public void resourceEntryChanged(FileResourceEntry entry) { TreePath parentPath = getPathToNode(entry).getParentPath(); ResourceTreeFolder parentFolder = (ResourceTreeFolder)parentPath.getLastPathComponent(); TreeModelEvent event = new TreeModelEvent(this, parentPath, new int[]{getIndexOfChild(parentFolder, entry)}, new Object[]{entry}); for (int i = 0; i < treeModelListeners.size(); i++) { treeModelListeners.get(i).treeNodesChanged(event); } } public int size() { int size = 0; for (int i = 0; i < root.getChildCount(); i++) { size += ((ResourceTreeFolder)root.getChild(i)).getChildCount(); } return size; } public void sort() { root.sortChildren(true); fireTreeStructureChanged(new TreePath(new Object[]{root})); } private void fireTreeStructureChanged(TreePath changed) { TreeModelEvent event = new TreeModelEvent(this, changed); for (int i = 0; i < treeModelListeners.size(); i++) { treeModelListeners.get(i).treeStructureChanged(event); } } }