/* * Copyright (C) 2010 Medo <smaxein@googlemail.com> * * This file is part of GmkSplitter. * GmkSplitter is free software and comes with ABSOLUTELY NO WARRANTY. * See LICENSE for details. */ package com.ganggarrison.gmdec; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; import java.util.List; import org.lateralgm.components.impl.ResNode; import org.lateralgm.file.GmFile; import org.lateralgm.resources.GameInformation; import org.lateralgm.resources.GameSettings; import org.lateralgm.resources.InstantiableResource; import org.lateralgm.resources.Resource; import com.ganggarrison.easyxml.XmlReader; import com.ganggarrison.gmdec.ResourceTreeEntry.Type; import com.ganggarrison.gmdec.dupes.InstanceAccessor; import com.ganggarrison.gmdec.dupes.OrderPreservingDupeRemoval; import com.ganggarrison.gmdec.dupes.TileAccessor; import com.ganggarrison.gmdec.files.ExtensionsFormat; import com.ganggarrison.gmdec.files.GameInfoFormat; import com.ganggarrison.gmdec.files.GameSettingsFormat; import com.ganggarrison.gmdec.files.ResourceFormat; import com.ganggarrison.gmdec.xml.ResourceListXmlFormat; public class ResourceReader { private static final PrimaryResourceType[] resTypeReadingOrder = new PrimaryResourceType[] { PrimaryResourceType.BACKGROUNDS, PrimaryResourceType.FONTS, PrimaryResourceType.SCRIPTS, PrimaryResourceType.SPRITES, PrimaryResourceType.SOUNDS, PrimaryResourceType.OBJECTS, PrimaryResourceType.ROOMS, PrimaryResourceType.PATHS, PrimaryResourceType.TIMELINES }; private final EnumMap<PrimaryResourceType, List<Resource<?, ?>>> resources; public ResourceReader() { resources = new EnumMap<PrimaryResourceType, List<Resource<?, ?>>>(PrimaryResourceType.class); for (PrimaryResourceType prt : PrimaryResourceType.values()) { resources.put(prt, new ArrayList<Resource<?, ?>>()); } } public void readTree(ResNode root, GmFile gmf, File sourcePath) throws IOException { DeferredReferenceCreatorNotifier notifier = new DeferredReferenceCreatorNotifier(); EnumMap<PrimaryResourceType, ResNode> primaryNodes = new EnumMap<PrimaryResourceType, ResNode>( PrimaryResourceType.class); for (PrimaryResourceType prt : PrimaryResourceType.values()) { ResNode typeNode = root.addChild(prt.pathName, ResNode.STATUS_PRIMARY, prt.resourceKind); primaryNodes.put(prt, typeNode); } for (PrimaryResourceType prt : resTypeReadingOrder) { File subdir = new File(sourcePath, prt.pathName); if (subdir.isDirectory()) { new SubtreeReader(prt, notifier).readSubtree(primaryNodes.get(prt), subdir); } } GameInfoFormat gameInfoFormat = new GameInfoFormat(); GameInformation gameInfo = gameInfoFormat.read(sourcePath, null, notifier); gameInfoFormat.addResToTree(gameInfo, root); gameInfoFormat.addAllResourcesToGmFile(Collections.singletonList(gameInfo), gmf); GameSettingsFormat gameSettingsFormat = new GameSettingsFormat(); GameSettings gameSettings = gameSettingsFormat.read(sourcePath, null, notifier); gameSettingsFormat.addResToTree(gameSettings, root); gameSettingsFormat.addAllResourcesToGmFile(Collections.singletonList(gameSettings), gmf); ExtensionsFormat extensionsFormat = new ExtensionsFormat(); List<String> extensions = extensionsFormat.read(sourcePath, null, notifier); extensionsFormat.addResToTree(extensions, root); extensionsFormat.addAllResourcesToGmFile(Collections.singletonList(extensions), gmf); for (PrimaryResourceType prt : PrimaryResourceType.values()) { addAllResourcesToGmFile(prt.format, resources.get(prt), gmf); } OrderPreservingDupeRemoval.perform(new TileAccessor(gmf)); OrderPreservingDupeRemoval.perform(new InstanceAccessor(gmf)); notifier.createReferences(gmf); } @SuppressWarnings("unchecked") private <T extends InstantiableResource<T, ?>> void addAllResourcesToGmFile(ResourceFormat<T> format, List<?> resources, GmFile gmf) { format.addAllResourcesToGmFile((List<T>) resources, gmf); } private class SubtreeReader { private final PrimaryResourceType prt; private final DeferredReferenceCreatorNotifier notifier; public SubtreeReader(PrimaryResourceType type, DeferredReferenceCreatorNotifier notifier) { this.prt = type; this.notifier = notifier; } public void readSubtree(ResNode node, File dir) throws IOException { List<ResourceTreeEntry> resources = readResourceList(dir); for (ResourceTreeEntry rte : resources) { if (rte.type == Type.GROUP) { String childName = rte.name; File subdir = new File(dir, rte.getFilename()); ResNode child = node.addChild(childName, ResNode.STATUS_GROUP, node.kind); if (!subdir.isDirectory()) { throw new IOException("Resource group directory: " + rte.getFilename() + " not found!"); } readSubtree(child, subdir); } else { readResource(dir, rte, node, prt); } } } private void readResource(File dir, ResourceTreeEntry entry, ResNode node, PrimaryResourceType prt) throws IOException { Resource<?, ?> resource = readResource(dir, entry, node, prt.format); resources.get(prt).add(resource); } private <T extends InstantiableResource<T, ?>> T readResource(File dir, ResourceTreeEntry entry, ResNode node, ResourceFormat<T> format) throws IOException { T res = format.read(dir, entry, notifier); format.addResToTree(res, node); return res; } /** * Read the resource list file and return the list of resources and * groups in this path. * * For completeness, this function scans the directory for any files and * directories that could be resources or groups but aren't accounted * for. A warning is written to stderr for these. This function could be * changed to actually add additional resources and groups to the end of * the list and remove nonexistant resources from it, for higher * tolerance to manual editing of the resource tree. * * @param subdir * @return * @throws IOException */ private List<ResourceTreeEntry> readResourceList(File subdir) throws IOException { File listFile = new File(subdir, "_resources.list.xml"); if (!listFile.isFile()) { System.err.print("WARNING: Directory " + subdir + " doesn't contain a resource list file. "); System.err.println("No resources from this directory or its subdirectories will be processed."); return Collections.emptyList(); } List<ResourceTreeEntry> resources = new ResourceListXmlFormat().read(new XmlReader(listFile)); File[] resFiles = subdir.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { String filename = pathname.getName().toLowerCase(); if (pathname.isDirectory()) { return !filename.contains("."); } else if (pathname.isFile()) { return (filename.endsWith(".xml") || filename.endsWith(".gml")) && !filename.equals("_resources.list.xml"); } else { return false; } } }); for (File resFile : resFiles) { if (!resourcesContainFile(resources, resFile)) { System.err.println("Warning: Potential resource/group not in list file and won't be included: " + resFile); } } return resources; } private boolean resourcesContainFile(List<ResourceTreeEntry> resources, File resFile) { String resName = getResourceName(resFile); Type resType = resFile.isDirectory() ? Type.GROUP : Type.RESOURCE; for (ResourceTreeEntry rte : resources) { if (rte.getFilename().equals(resName) && rte.type == resType) { return true; } } return false; } private String getResourceName(File resFile) { String name = resFile.getName(); String lowName = name.toLowerCase(); if (resFile.isFile() && (lowName.endsWith(".xml") || lowName.endsWith(".gml"))) { name = name.substring(0, name.length() - 4); } return name; } } }