/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package com.bc.ceres.core.runtime.internal; import com.bc.ceres.core.Assert; import com.bc.ceres.core.CoreException; import com.bc.ceres.core.runtime.ModuleState; import com.bc.ceres.core.runtime.Version; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.XStreamException; import com.thoughtworks.xstream.converters.*; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.copy.HierarchicalStreamCopier; import com.thoughtworks.xstream.io.xml.XppDomReader; import com.thoughtworks.xstream.io.xml.XppDomWriter; import com.thoughtworks.xstream.io.xml.xppdom.XppDom; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.lang.reflect.Field; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.regex.Pattern; /** * A reader for module XML files. */ public class ModuleManifestParser { private static final Pattern REPLACE_WHITESPACE_PATTERN = Pattern.compile("\\s{2,}"); public ModuleManifestParser() { } public ModuleImpl parse(String xml) throws CoreException { Assert.notNull(xml, "xml"); try { ModuleImpl module = (ModuleImpl) createXStream().fromXML(xml); postProcessModule(module); return module; } catch (XStreamException e) { throw toCoreException(e); } } public ModuleImpl parse(InputStream stream) throws CoreException { Assert.notNull(stream, "stream"); try { ModuleImpl module = (ModuleImpl) createXStream().fromXML(stream); postProcessModule(module); return module; } catch (XStreamException e) { throw toCoreException(e); } finally { try { stream.close(); } catch (IOException e) { // ignore } } } public ModuleImpl parse(Reader reader) throws CoreException { Assert.notNull(reader, "reader"); try { ModuleImpl module = (ModuleImpl) createXStream().fromXML(reader); postProcessModule(module); return module; } catch (XStreamException e) { throw toCoreException(e); } finally { try { reader.close(); } catch (IOException e) { // ignore } } } private static void postProcessModule(ModuleImpl module) throws CoreException { trimStringFields(module); module.setModuleId(-1); if (module.getManifestVersion() == null) { throw new CoreException("Missing manifest version"); } if (module.getSymbolicName() == null) { throw new CoreException("Missing module identifier"); } if (module.getSymbolicName().length() == 0) { throw new CoreException("Empty module identifier"); } if (module.getVersion() == null) { module.setVersion(Version.parseVersion("1.0")); } if (module.getCategoriesString() != null) { module.setCategories(toArray(module.getCategoriesString())); } else { module.setCategories(new String[0]); } if (module.getPackaging() == null) { module.setPackaging("jar"); } if (module.getActivatorClassName() == null) { module.setActivatorClassName(DefaultActivator.class.getName()); } module.setState(ModuleState.NULL); module.initDeclaredComponents(); } private static void trimStringFields(ModuleImpl module) { Field[] declaredFields = ModuleImpl.class.getDeclaredFields(); List<Field> stringFields = new ArrayList<Field>(); for (Field field : declaredFields) { if (field.getType().equals(String.class)) { stringFields.add(field); } } Field.setAccessible(stringFields.toArray(new Field[stringFields.size()]), true); try { for (Field field : stringFields) { try { String value = (String) field.get(module); if (value != null) { field.set(module, trim(value)); } } catch (IllegalAccessException e) { // ignore } } } finally { Field.setAccessible(stringFields.toArray(new Field[stringFields.size()]), false); } } /* * Replace all sequenced whitespace characters with a single whitespace character. */ private static String trim(String value) { return REPLACE_WHITESPACE_PATTERN.matcher(value.trim()).replaceAll(" "); } private static XStream createXStream() { XStream xstream = new XStream(); // Module xstream.alias("module", ModuleImpl.class); xstream.aliasField("activator", ModuleImpl.class, "activatorClassName"); xstream.aliasField("dependencies", ModuleImpl.class, "declaredDependencies"); xstream.aliasField("categories", ModuleImpl.class, "categoriesString"); xstream.aliasField("native", ModuleImpl.class, "usingJni"); xstream.omitField(ModuleImpl.class, "state"); xstream.omitField(ModuleImpl.class, "activator"); xstream.omitField(ModuleImpl.class, "registry"); xstream.omitField(ModuleImpl.class, "location"); xstream.omitField(ModuleImpl.class, "context"); xstream.omitField(ModuleImpl.class, "privateClasspath"); xstream.omitField(ModuleImpl.class, "declaredLibs"); xstream.omitField(ModuleImpl.class, "impliciteLibs"); xstream.omitField(ModuleImpl.class, "impliciteNativeLibs"); xstream.registerConverter(new VersionConverter()); // Dependency xstream.alias("dependency", DependencyImpl.class); xstream.aliasField("lib", DependencyImpl.class, "libName"); xstream.aliasField("module", DependencyImpl.class, "moduleSymbolicName"); xstream.omitField(DependencyImpl.class, "declaringModule"); // ExtensionPoint xstream.alias("extensionPoint", ExtensionPointImpl.class); xstream.registerConverter(new ExtensionPointConverter()); // Extension xstream.alias("extension", ExtensionImpl.class); xstream.registerConverter(new ExtensionConverter()); xstream.useAttributeFor("id", String.class); xstream.useAttributeFor("point", String.class); xstream.addImplicitCollection(ModuleImpl.class, "declaredDependencies", "dependency", DependencyImpl.class); xstream.addImplicitCollection(ModuleImpl.class, "extensions", "extension", ExtensionImpl.class); xstream.addImplicitCollection(ModuleImpl.class, "extensionPoints", "extensionPoint", ExtensionPointImpl.class); return xstream; } private static XppDom readDom(HierarchicalStreamReader source) { XppDomWriter destination = new XppDomWriter(); new HierarchicalStreamCopier().copy(source, destination); return destination.getConfiguration(); } private static void writeDom(XppDom dom, HierarchicalStreamWriter destination) { XppDom[] children = dom.getChildren(); for (XppDom child : children) { new HierarchicalStreamCopier().copy(new XppDomReader(child), destination); } } private static String[] toArray(String csvString) { StringTokenizer stringTokenizer = new StringTokenizer(csvString, ",", false); ArrayList<String> stringList = new ArrayList<String>(8); while (stringTokenizer.hasMoreElements()) { String stringElement = stringTokenizer.nextElement().toString().trim(); if (stringElement.length() > 0) { stringList.add(stringElement); } } return stringList.toArray(new String[stringList.size()]); } private CoreException toCoreException(XStreamException e) { return new CoreException("Failed to parse module manifest: " + e.getMessage(), e); } private static class ExtensionConverter implements Converter { public boolean canConvert(Class aClass) { return aClass.equals(ExtensionImpl.class); } public void marshal(Object object, HierarchicalStreamWriter destination, MarshallingContext marshallingContext) { ExtensionImpl extension = (ExtensionImpl) object; destination.addAttribute("id", extension.getId()); destination.addAttribute("point", extension.getPoint()); ConfigurationElementImpl configurationElementImpl = (ConfigurationElementImpl) extension.getConfigurationElement(); writeDom(configurationElementImpl.getDom(), destination); } public Object unmarshal(HierarchicalStreamReader source, UnmarshallingContext unmarshallingContext) { String id = source.getAttribute("id"); String point = source.getAttribute("point"); if (point == null) { throw new ConversionException( MessageFormat.format("element [{0}]: missing attribute [point]", source.getNodeName())); } XppDom dom = readDom(source); return new ExtensionImpl(point, new ConfigurationElementImpl(null, dom), id); } } private static class VersionConverter implements SingleValueConverter { public boolean canConvert(Class aClass) { return Version.class.equals(aClass); } public Object fromString(String string) { if (string.length() == 0) { throw new ConversionException("empty version string"); } if (!Character.isDigit(string.charAt(0))) { throw new ConversionException("invalid version string"); } return Version.parseVersion(string); } public String toString(Object object) { return object.toString(); } } private static class ExtensionPointConverter implements Converter { public boolean canConvert(Class aClass) { return aClass.equals(ExtensionPointImpl.class); } public void marshal(Object object, HierarchicalStreamWriter destination, MarshallingContext marshallingContext) { ExtensionPointImpl extensionPoint = (ExtensionPointImpl) object; destination.addAttribute("id", extensionPoint.getId()); ConfigurationSchemaElementImpl configurationElementImpl = (ConfigurationSchemaElementImpl) extensionPoint.getConfigurationSchemaElement(); writeDom(configurationElementImpl.getDom(), destination); } public Object unmarshal(HierarchicalStreamReader source, UnmarshallingContext unmarshallingContext) { String id = source.getAttribute("id"); if (id == null) { throw new ConversionException( MessageFormat.format("element [{0}]: missing attribute [id]", source.getNodeName())); } XppDom dom = readDom(source); return new ExtensionPointImpl(id, new ConfigurationSchemaElementImpl(null, dom)); } } }