/******************************************************************************* * Copyright (c) 2012, 2014 Wind River Systems, Inc. and others. All rights reserved. * This program and the accompanying materials are made available under the terms * of the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.te.runtime.persistence.delegates; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.tcf.te.runtime.extensions.ExecutableExtension; import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer; import org.eclipse.tcf.te.runtime.persistence.PersistenceManager; import org.eclipse.tcf.te.runtime.persistence.interfaces.IPersistenceDelegate; import org.eclipse.tcf.te.runtime.persistence.interfaces.IVariableDelegate; import org.eclipse.tcf.te.runtime.properties.PropertiesContainer; import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** * GsonMapPersistenceDelegate */ public class GsonMapPersistenceDelegate extends ExecutableExtension implements IPersistenceDelegate { private final String defaultFileExtension; protected static final String VARIABLES = "__VariablesMap__"; //$NON-NLS-1$ private final Gson gson = new GsonBuilder().setPrettyPrinting().enableComplexMapKeySerialization().create(); /** * Constructor. */ public GsonMapPersistenceDelegate() { this("json"); //$NON-NLS-1$ } /** * Constructor. */ public GsonMapPersistenceDelegate(String defaultFileExtension) { super(); Assert.isNotNull(defaultFileExtension); this.defaultFileExtension = defaultFileExtension; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.persistence.interfaces.IPersistenceDelegate#getPersistedClass(java.lang.Object) */ @Override public Class<?> getPersistedClass(Object context) { return Map.class; } /** * Return the default file extension if container is an URI. */ protected String getDefaultFileExtension() { return defaultFileExtension; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.persistence.interfaces.IPersistenceDelegate#writeList(java.lang.Object[], java.lang.Object) */ @Override public Object writeList(Object[] context, Object container) throws IOException { Assert.isNotNull(context); Assert.isNotNull(container); return write(context, container, true); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.persistence.interfaces.IPersistenceDelegate#write(java.lang.Object, java.lang.Object) */ @Override public final Object write(Object context, Object container) throws IOException { Assert.isNotNull(context); Assert.isNotNull(container); return write(context, container, false); } private Object write(Object context, Object container, boolean isList) throws IOException { Assert.isNotNull(context); Assert.isNotNull(container); if (container instanceof URI) { URI uri = (URI) container; // Only "file:" URIs are supported if (!"file".equalsIgnoreCase(uri.getScheme())) { //$NON-NLS-1$ throw new IOException("Unsupported URI schema '" + uri.getScheme() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ } // Create the file object from the given URI File file = new File(uri.normalize()); // The file must be absolute if (!file.isAbsolute()) { throw new IOException("URI must denote an absolute file path."); //$NON-NLS-1$ } // If the file defaultFileExtension is no set, default to "properties" IPath path = new Path(file.getCanonicalPath()); if (path.getFileExtension() == null) { file = path.addFileExtension(getDefaultFileExtension()).toFile(); } // Gson gson = new GsonBuilder().setPrettyPrinting().create(); Writer writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8"); //$NON-NLS-1$ if (!isList) { try { gson.toJson(internalToMap(context), Map.class, writer); } finally { writer.close(); } } else { List<String> encoded = new ArrayList<String>(); for (Object entry : (Object[]) context) { encoded.add(gson.toJson(internalToMap(entry))); } try { gson.toJson(encoded, List.class, writer); } finally { writer.close(); } } } else if (String.class.equals(container)) { // Gson gson = new GsonBuilder().create(); if (!isList) { container = gson.toJson(internalToMap(context)); } else { List<String> encoded = new ArrayList<String>(); for (Object entry : (Object[]) context) { encoded.add(gson.toJson(internalToMap(entry))); } container = gson.toJson(encoded); } } return container; } /* * Convert the context to a Map, extract and use variables and add them to the map as key * VARIABLE. */ private Map<String, Object> internalToMap(Object context) { try { Map<String, Object> data = toMap(context); if (data != null) { Map<String, String> variables = null; IVariableDelegate[] delegates = PersistenceManager.getInstance().getVariableDelegates(this); for (IVariableDelegate delegate : delegates) { variables = delegate.getVariables(data); } if (variables != null && !variables.isEmpty()) { data.put(VARIABLES, variables); } } Map<String, Object> sorted = new TreeMap<String, Object>(data); return sorted; } catch (Exception e) { } return null; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.persistence.interfaces.IPersistenceDelegate#readList(java.lang.Class, java.lang.Object) */ @Override public Object[] readList(Class<?> contextClass, Object container) throws IOException { Assert.isNotNull(container); return (Object[])read(contextClass, container, true); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.persistence.interfaces.IPersistenceDelegate#read(java.lang.Object, java.lang.Object) */ @Override public final Object read(Object context, Object container) throws IOException { Assert.isNotNull(container); return read(context, container, false); } @SuppressWarnings("unchecked") private Object read(Object context, Object container, boolean isList) throws IOException { Assert.isNotNull(container); // Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create(); List<Map<String, Object>> data = new ArrayList<Map<String, Object>>(); if (container instanceof URI) { URI uri = (URI) container; // Only "file:" URIs are supported if (!"file".equalsIgnoreCase(uri.getScheme())) { //$NON-NLS-1$ throw new IOException("Unsupported URI schema '" + uri.getScheme() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ } // Create the file object from the given URI File file = new File(uri.normalize()); // The file must be absolute if (!file.isAbsolute()) { throw new IOException("URI must denote an absolute file path."); //$NON-NLS-1$ } if (!file.exists()) { IPath path = new Path(file.getCanonicalPath()); if (path.getFileExtension() == null) { file = path.addFileExtension(getDefaultFileExtension()).toFile(); } } Reader reader = new InputStreamReader(new FileInputStream(file), "UTF-8"); //$NON-NLS-1$ if (!isList) { try { Map<String,Object> read = gson.fromJson(reader, Map.class); data.add(read); } finally { reader.close(); } } else { try { List<String> strings = gson.fromJson(reader, List.class); for (String string : strings) { Map<String,Object> read = gson.fromJson(string, Map.class); data.add(read); } } finally { reader.close(); } } } else if (container instanceof String) { if (!isList) { data.add(gson.fromJson((String) container, Map.class)); } else { List<String> strings = gson.fromJson((String) container, List.class); for (String string : strings) { data.add(gson.fromJson(string, Map.class)); } } } for (Map<String, Object> entry : data) { if (entry != null) { Map<String, String> variables = new HashMap<String, String>(); if (entry.containsKey(VARIABLES)) { variables = (Map<String, String>) entry.remove(VARIABLES); } IVariableDelegate[] delegates = PersistenceManager.getInstance().getVariableDelegates(this); for (IVariableDelegate delegate : delegates) { entry = delegate.putVariables(entry, variables); } } } if (!isList) { return !data.isEmpty() && data.get(0) != null ? fromMap(data.get(0), context) : context; } List<Object> list = new ArrayList<Object>(); for (Map<String, Object> entry : data) { list.add(fromMap(entry, context)); } return list.toArray(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.persistence.interfaces.IPersistenceDelegate#delete(java.lang.Object, java.lang.Object) */ @Override public boolean delete(Object context, Object container) throws IOException { Assert.isNotNull(container); if (container instanceof URI) { URI uri = (URI) container; // Only "file:" URIs are supported if (!"file".equalsIgnoreCase(uri.getScheme())) { //$NON-NLS-1$ throw new IOException("Unsupported URI schema '" + uri.getScheme() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ } // Create the file object from the given URI File file = new File(uri.normalize()); // The file must be absolute if (!file.isAbsolute()) { throw new IOException("URI must denote an absolute file path."); //$NON-NLS-1$ } if (!file.exists()) { IPath path = new Path(file.getCanonicalPath()); if (path.getFileExtension() == null) { file = path.addFileExtension(getDefaultFileExtension()).toFile(); } } // If the file defaultFileExtension is no set, default to "properties" IPath path = new Path(file.getCanonicalPath()); if (path.getFileExtension() == null) { file = path.addFileExtension(getDefaultFileExtension()).toFile(); } return file.delete(); } return false; } /** * Convert the given context to map. * * @param context The context. Must not be <code>null</code>. * @return Map representing the context. * * @throws IOException */ @SuppressWarnings("unchecked") protected Map<String, Object> toMap(final Object context) throws IOException { Map<String, Object> result = new HashMap<String, Object>(); Map<String, Object> attrs = null; if (context instanceof Map) { attrs = (Map<String, Object>) context; } else if (context instanceof IPropertiesContainer) { IPropertiesContainer container = (IPropertiesContainer) context; attrs = new HashMap<String, Object>(container.getProperties()); } if (attrs != null) { for (Entry<String, Object> entry : attrs.entrySet()) { if (!entry.getKey().endsWith(".transient")) { //$NON-NLS-1$ result.put(entry.getKey(), entry.getValue()); } } } return result; } /** * Convert a map into the needed context object. * * @param map The map representing the context. Must not be <code>null</code>. * @param context The context to put the map values in or <code>null</code>. * @return The context object. * * @throws IOException */ protected Object fromMap(Map<String, Object> map, Object context) throws IOException { if (context == null || Map.class.equals(context)) { return map; } else if (context instanceof Map) { @SuppressWarnings({ "rawtypes", "unchecked" }) Map<String, Object> newMap = new HashMap<String, Object>((Map) context); newMap.putAll(map); return newMap; } else if (IPropertiesContainer.class.equals(context)) { IPropertiesContainer container = new PropertiesContainer(); container.setProperties(map); return container; } else if (context instanceof IPropertiesContainer) { IPropertiesContainer container = (IPropertiesContainer) context; container.setProperties(map); return container; } return null; } }