/*
* Copyright (c) 2014 tabletoptool.com team.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* rptools.com team - initial implementation
* tabletoptool.com team - further development
*/
package com.t3.model;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import com.t3.client.TabletopTool;
/**
* Model for arranging assets in a hierarchical way
*/
public class AssetGroup {
private final String name;
private final File location;
private boolean groupsLoaded;
private boolean filesLoaded;
// Asset refresh data
private Map<File, AssetTS> assetTSMap = new HashMap<File, AssetTS>();
// Group refresh data
private Map<File, AssetGroup> assetGroupTSMap = new HashMap<File, AssetGroup>();
private final List<Asset> assetList = new ArrayList<Asset>();
private final List<AssetGroup> assetGroupList = new ArrayList<AssetGroup>();
private static final FilenameFilter IMAGE_FILE_FILTER = new FilenameFilter() {
private Pattern extensionPattern = null;
private String[] extensions = new String[] { "bmp", "gif", "png", "jpg", "jpeg" };
@Override
public boolean accept(File dir, String name) {
if (extensionPattern == null) {
// Setup defaults, then override if we have Java 1.6+
if (TabletopTool.JAVA_VERSION >= 1.6) {
try {
Class<?> imageIO = Class.forName("javax.imageio.ImageIO");
Method getReaderFileSuffixes = imageIO.getDeclaredMethod("getReaderFileSuffixes", (Class[]) null);
Object result = getReaderFileSuffixes.invoke(null, (Object[]) null);
extensions = (String[]) result;
// extensions = ImageIO.getReaderFileSuffixes();
} catch (Exception e) {
// NoSuchMethodException
// ClassNotFoundException
// IllegalAccessException
// InvocationTargetException
}
}
String list = Arrays.deepToString(extensions);
// Final result is something like: \.(jpeg|jpg|bmp|wbmp|png|gif|tiff)$
String pattern = "\\." + list.replace('[', '(').replace(']', ')').replace(", ", "|") + "$";
extensionPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
}
return extensionPattern.matcher(name).find();
}
};
private static final FilenameFilter DIRECTORY_FILE_FILTER = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return new File(dir.getPath() + File.separator + name).isDirectory();
}
};
public AssetGroup(File location, String name) {
assert name != null : "Name cannot be null";
this.location = location;
this.name = name;
groupsLoaded = false;
filesLoaded = false;
}
public String getName() {
return name;
}
public boolean hasChildGroups() {
loadGroupData();
return !assetGroupList.isEmpty();
}
public boolean hasAssets() {
loadFileData();
return !assetList.isEmpty();
}
public int getChildGroupCount() {
loadGroupData();
return assetGroupList.size();
}
public int getAssetCount() {
loadFileData();
return assetList.size();
}
public int indexOf(Asset asset) {
loadFileData();
return assetList.indexOf(asset);
}
public int indexOf(AssetGroup group) {
loadGroupData();
return assetGroupList.indexOf(group);
}
/**
*/
public List<AssetGroup> getChildGroups() {
loadGroupData();
return Collections.unmodifiableList(assetGroupList);
}
/**
*/
public List<Asset> getAssets() {
loadFileData();
return Collections.unmodifiableList(assetList);
}
public void add(AssetGroup group) {
assetGroupList.add(group);
assetGroupTSMap.put(group.location, group);
// Keeps the groups ordered
Collections.sort(assetGroupList, GROUP_COMPARATOR);
}
public void remove(AssetGroup group) {
assetGroupList.remove(group);
assetGroupTSMap.remove(group.location);
}
@Override
public String toString() {
return "AssetGroup[" + name + "]: " + assetList.size() + " assets and " + assetGroupList.size() + " groups";
}
/**
* Release the assets and groups so that they can be garbage collected.
*/
private void clear() {
assetTSMap.clear();
assetGroupTSMap.clear();
assetList.clear();
for (AssetGroup group : assetGroupList) {
group.clear();
}
}
private synchronized void loadGroupData() {
if (!groupsLoaded) {
// Copy the asset and group files map so that files that were deleted go away.
Map<File, AssetGroup> tempAssetGroupFiles = assetGroupTSMap;
assetGroupTSMap = new HashMap<File, AssetGroup>();
assetGroupList.clear();
try {
// Update subgroups
File[] subdirArray = location.listFiles(DIRECTORY_FILE_FILTER);
for (File subdir : subdirArray) {
// Get the group or create a new one
AssetGroup subgroup = tempAssetGroupFiles.get(subdir);
if (subgroup == null) {
subgroup = new AssetGroup(subdir, subdir.getName());
} else {
tempAssetGroupFiles.remove(subdir);
}
assetGroupTSMap.put(subdir, subgroup);
assetGroupList.add(subgroup);
}
Collections.sort(assetGroupList, GROUP_COMPARATOR);
} finally {
// Cleanup
for (AssetGroup group : tempAssetGroupFiles.values()) {
group.clear();
}
tempAssetGroupFiles.clear();
}
groupsLoaded = true;
}
}
private synchronized void loadFileData() {
if (!filesLoaded) {
// Copy the asset and group files map so that files that were deleted go away.
Map<File, AssetTS> tempAssetFiles = assetTSMap;
assetTSMap = new HashMap<File, AssetTS>();
assetList.clear();
try {
// Update images for this group
File[] imageFileArray = location.listFiles(IMAGE_FILE_FILTER);
if (imageFileArray != null) {
for (File file : imageFileArray) {
// Latest file already in the group?
AssetTS data = tempAssetFiles.get(file);
if (data != null && data.lastModified == file.lastModified()) {
assetTSMap.put(file, data);
tempAssetFiles.remove(file);
assetList.add(data.asset);
continue;
}
// Get the asset, is it already in the game?
try {
Asset asset = AssetManager.createAsset(file);
if (AssetManager.hasAsset(asset.getId())) {
asset = AssetManager.getAsset(asset.getId());
}
// Add the asset
assetTSMap.put(file, new AssetTS(asset, file.lastModified()));
assetList.add(asset);
} catch (IOException ioe) {
// TODO: Handle this better
ioe.printStackTrace();
}
}
}
} finally {
// Cleanup
tempAssetFiles.clear();
}
filesLoaded = true;
}
}
/**
* This method will cause the assets to be updated the next time one is read. The child groups are updated as well.
*/
public void updateGroup() {
groupsLoaded = false;
filesLoaded = false;
for (AssetGroup group : assetGroupList)
group.updateGroup();
}
private static class AssetTS {
public Asset asset;
public long lastModified;
public AssetTS(Asset asset, long lastModified) {
this.asset = asset;
this.lastModified = lastModified;
}
}
public static final Comparator<AssetGroup> GROUP_COMPARATOR = new Comparator<AssetGroup>() {
@Override
public int compare(AssetGroup o1, AssetGroup o2) {
return o1.getName().toUpperCase().compareTo(o2.getName().toUpperCase());
}
};
}