/** * JBoss, Home of Professional Open Source * Copyright 2009, JBoss Inc., and others contributors as indicated * by the @authors tag. All rights reserved. * See the copyright.txt in the distribution for a * full listing of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * (C) 2009, JBoss Inc. */ package org.jboss.tools.smooks.launch.serialize; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.milyn.payload.JavaResult; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.AbstractXmlWriter; import com.thoughtworks.xstream.io.xml.DomDriver; /** * Object Graph Serializer. * * @author <a href="mailto:tom.fennelly@jboss.com">tom.fennelly@jboss.com</a> */ public class ObjectSerializer extends AbstractXmlWriter { private Object rootObject; private String rootBeanId; private List<Object> items = new ArrayList<Object>(); private Set<String> referencedBeans = new LinkedHashSet<String>(); private Map<String, Object> beanContext; private int indent = 0; private boolean writeNL = false; private String serializedForm; private StringWriter writer; public ObjectSerializer(Object object, String beanId, Map<String, Object> beanMap) { assertNotNull(object, "object"); //$NON-NLS-1$ assertNotNull(beanId, "beanId"); //$NON-NLS-1$ assertNotNull(beanMap, "beanMap"); //$NON-NLS-1$ this.rootObject = object; this.rootBeanId = beanId; this.beanContext = beanMap; this.serializedForm = writeObject(); } public static String serialize(Object object, Map<String, Object> beanMap) { ObjectSerializer serializer = new ObjectSerializer(object, object.getClass().getSimpleName(), beanMap); return serializer.getSerializedForm(); } public static Collection<ObjectSerializer> serialize(JavaResult javaResult) { StringBuilder stringBuilder = new StringBuilder(); Map<String, Object> beanMap = javaResult.getResultMap(); Set<Entry<String, Object>> beanMapEntries = beanMap.entrySet(); Map<String, ObjectSerializer> serializedObjectMap = new HashMap<String, ObjectSerializer>(); Set<String> beanNames = beanMap.keySet(); // Create all the ObjectSerializer instances... for(Entry<String, Object> beanMapEntry : beanMapEntries) { serializedObjectMap.put(beanMapEntry.getKey(), new ObjectSerializer(beanMapEntry.getValue(), beanMapEntry.getKey(), beanMap)); } // Now try to work out which can be eliminated by analysing the beans used in each // ObjectSerializer instance... Set<Entry<String, ObjectSerializer>> serializedObjectMapEntries = serializedObjectMap.entrySet(); for(Entry<String, ObjectSerializer> serializedObjectMapEntry : serializedObjectMapEntries) { beanNames.removeAll(serializedObjectMapEntry.getValue().getReferencedBeans()); } if(!beanNames.isEmpty()) { // If the beanNames Set is not empty, only return those ObjectSerializer instances... List<ObjectSerializer> rootBeans = new ArrayList<ObjectSerializer>(); for(String beanName : beanNames) { rootBeans.add(serializedObjectMap.get(beanName)); } return rootBeans; } else { // If the beanNames Set is empty, return all ObjectSerializer instances i.e. // we're not able to work out the "root" bean(s)... return serializedObjectMap.values(); } } public Set<String> getReferencedBeans() { return referencedBeans; } public String getSerializedForm() { return serializedForm; } private synchronized String writeObject() { XStream xstream = new XStream(new DomDriver()); MarshallingStrategy marshallingStrategy = new MarshallingStrategy(this); writer = new StringWriter(); items.clear(); referencedBeans.clear(); xstream.setMarshallingStrategy(marshallingStrategy); xstream.marshal(rootObject, this); return writer.toString(); } /* (non-Javadoc) * @see com.thoughtworks.xstream.io.xml.AbstractXmlWriter#startNode(java.lang.String, java.lang.Class) */ @Override public void startNode(String name, Class clazz) { if(writeNL) { writer.write("\n"); //$NON-NLS-1$ } if(indent == 0) { // Root object... writer.write("> " + rootBeanId); //$NON-NLS-1$ } else { writeIndent(indent); writer.write("> " + name); //$NON-NLS-1$ } writeNL = true; indent++; } public void startNode(Object item) { //write(" (@" + Integer.toHexString(System.identityHashCode(item)) + ")"); items.add(item); String beanId = getBeanId(item, true); if(beanId != null) { writer.write(" (beanId = \"" + beanId + "\")"); //$NON-NLS-1$ //$NON-NLS-2$ if(!beanId.equals(rootBeanId)) { referencedBeans.add(beanId); } } } private String getBeanId(Object item, boolean checkCollections) { // First see it it one of the root beans... Set<Entry<String, Object>> beans = beanContext.entrySet(); for(Entry<String, Object> bean : beans) { if(bean.getValue() == item) { return bean.getKey(); } } if(checkCollections) { // Then, see is it in a Collection bean in one of the root beans... for(Entry<String, Object> bean : beans) { Object beanObj = bean.getValue(); if(beanObj instanceof Collection) { Collection collectionBean = (Collection)beanObj; if(collectionContains(collectionBean, item)) { // Get the beanId of the first object in the collection // that also exists in the root of the beanContext... for (Object collectionItem : collectionBean) { String beanId = getBeanId(collectionItem, false); if(beanId != null) { return beanId; } } } } } } return null; } private boolean collectionContains(Collection collection, Object item) { // Intentionally not using Collection.contains() because it uses // Object.equals(), which is not what we want... we need an exact object ref match... for(Object object : collection) { if(object == item) { return true; } } return false; } /* (non-Javadoc) * @see com.thoughtworks.xstream.io.HierarchicalStreamWriter#setValue(java.lang.String) */ public void setValue(String text) { Object currentItem = items.get(items.size() - 1); if((currentItem instanceof String || containsNonNumeric(text)) && !(currentItem instanceof Number)) { writer.write(" = \"" + text + "\""); //$NON-NLS-1$ //$NON-NLS-2$ } else if(currentItem instanceof Number) { Class<? extends Object> itemClass = currentItem.getClass(); if(itemClass.getPackage() == Integer.class.getPackage()) { writer.write(" = " + text + itemClass.getSimpleName().charAt(0)); //$NON-NLS-1$ } else { writer.write(" = " + text); //$NON-NLS-1$ } } else { writer.write(" = " + text); //$NON-NLS-1$ } writer.write("\n"); //$NON-NLS-1$ writeNL = false; } /* (non-Javadoc) * @see com.thoughtworks.xstream.io.HierarchicalStreamWriter#endNode() */ public void endNode() { indent--; } /* (non-Javadoc) * @see com.thoughtworks.xstream.io.HierarchicalStreamWriter#startNode(java.lang.String) */ public void startNode(String name) { } /* (non-Javadoc) * @see com.thoughtworks.xstream.io.HierarchicalStreamWriter#flush() */ public void flush() { writer.flush(); } private void writeIndent(int count) { for(int i = 0; i < count; i++) { writer.write(" "); //$NON-NLS-1$ } } /* (non-Javadoc) * @see com.thoughtworks.xstream.io.HierarchicalStreamWriter#addAttribute(java.lang.String, java.lang.String) */ public void addAttribute(String name, String value) { } /* (non-Javadoc) * @see com.thoughtworks.xstream.io.HierarchicalStreamWriter#close() */ public void close() { } private boolean containsNonNumeric(String text) { for(int i = 0; i < text.length(); i++) { if(!Character.isDigit(text.charAt(i))) { return true; } } return false; } private void assertNotNull(Object object, String name) { if(object == null) { throw new IllegalArgumentException("ObjectSerializer argument '" + name + "' is null."); //$NON-NLS-1$ //$NON-NLS-2$ } } }