/* $Id$ * * Copyright (c) 2008 by Brent Easton and Joel Uckelman * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.build.module.metadata; import java.io.BufferedInputStream; import java.io.IOException; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import VASSAL.build.GameModule; import VASSAL.build.module.ModuleExtension; import VASSAL.tools.ArchiveWriter; import VASSAL.tools.io.IOUtils; public class ExtensionMetaData extends AbstractMetaData { private static final Logger logger = LoggerFactory.getLogger(ExtensionMetaData.class); public static final String ZIP_ENTRY_NAME = "extensiondata"; public static final String DATA_VERSION = "1"; protected static final String UNIVERSAL_ELEMENT = "universal"; protected static final String UNIVERSAL_ATTR = "anyModule"; protected ModuleMetaData moduleData; protected boolean universal; /** * Build an ExtensionMetaData for the given extension * * @param ext * Extension */ public ExtensionMetaData(ModuleExtension ext) { super(); setVersion(ext.getVersion()); setDescription( new Attribute(ModuleExtension.DESCRIPTION, ext.getDescription())); universal = ext.getUniversal(); } /** * Read Extension metadata from specified zip archive * * @param zip the archive */ public ExtensionMetaData(ZipFile zip) { read(zip); } public String getModuleName() { return moduleData == null ? "" : moduleData.getName(); } public String getModuleVersion() { return moduleData == null ? "" : moduleData.getVersion(); } public String getZipEntryName() { return ZIP_ENTRY_NAME; } public String getMetaDataVersion() { return DATA_VERSION; } /** * Write Extension metadata to the specified Archive * @param archive Save game Archive * @throws IOException If anything goes wrong */ public void save(ArchiveWriter archive) throws IOException { super.save(archive); // Also save a copy of the current module metadata in the save file. Copy // module metadata from the module archive as it will contain full i18n // information. copyModuleMetadata(archive); } /** * Add elements specific to an ExtensionMetaData * * @param doc Document * @param root Root element */ protected void addElements(Document doc, Element root) { final Element e = doc.createElement(UNIVERSAL_ELEMENT); e.appendChild(doc.createTextNode(String.valueOf(universal))); root.appendChild(e); } /** * Read and validate an Extension file. * - Check it has a Zip Entry named buildfile * - If it has a metadata file, read and parse it. * * @param file Module File */ public void read(ZipFile zip) { try { // Try to parse the metadata. Failure is not catastrophic, we can // treat it like an old-style module with no metadata and parse // the first lines of the buildFile. DefaultHandler handler = null; ZipEntry data = zip.getEntry(getZipEntryName()); if (data == null) { data = zip.getEntry(GameModule.BUILDFILE); handler = new ExtensionBuildFileXMLHandler(); } else { handler = new MetadataXMLHandler(); } // parse! parse! BufferedInputStream in = null; try { in = new BufferedInputStream(zip.getInputStream(data)); synchronized (parser) { parser.setContentHandler(handler); parser.setDTDHandler(handler); parser.setEntityResolver(handler); parser.setErrorHandler(handler); parser.parse(new InputSource(in)); } in.close(); } finally { IOUtils.closeQuietly(in); } // read the matching Module data. A basic moduledata may have been // built when reading the buildFile, overwrite if we find a real // module metadata file final ModuleMetaData buildFileModuleData = moduleData; moduleData = new ModuleMetaData(zip); if (moduleData == null) { moduleData = buildFileModuleData; } zip.close(); } catch (IOException e) { logger.error("", e); } catch (SAXEndException e) { // Indicates End of module/extension parsing. not an error. } catch (SAXException e) { logger.error("", e); } finally { IOUtils.closeQuietly(zip); } } /** * XML Handler for parsing an Extension metadata file */ private class MetadataXMLHandler extends XMLHandler { @Override public void endElement(String uri, String localName, String qName) { // handle all of the elements which have CDATA here if (UNIVERSAL_ELEMENT.equals(qName)) { universal = "true".equals(accumulator.toString().trim()); } else { super.endElement(uri, localName, qName); } } } /** * XML Handle for parsing an extension buildFile. Used to read minimal data from * extensions saved prior to 3.1.0. */ private class ExtensionBuildFileXMLHandler extends BuildFileXMLHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXEndException { super.startElement(uri, localName, qName, attrs); // handle element attributes we care about if (BUILDFILE_EXTENSION_ELEMENT.equals(qName)) { setVersion(getAttr(attrs, VERSION_ATTR)); setVassalVersion(getAttr(attrs, VASSAL_VERSION_ATTR)); setDescription(getAttr(attrs, DESCRIPTION_ATTR)); universal = "true".equals(getAttr(attrs, UNIVERSAL_ATTR)); // Build a basic module metadata in case this is an older extension // with no metadata from the originating module final String moduleName = getAttr(attrs, MODULE_NAME_ATTR); final String moduleVersion = getAttr(attrs, MODULE_VERSION_ATTR); moduleData = new ModuleMetaData(moduleName, moduleVersion); throw new SAXEndException(); } } } }