/*******************************************************************************
* Copyright (c) 2011 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:
* William Chen (Wind River) [360494]Provide an "Open With" action in the pop
* up menu of file system nodes of Target Explorer.
*******************************************************************************/
package org.eclipse.tm.te.tcf.filesystem.internal.handlers;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.tm.te.tcf.filesystem.activator.UIPlugin;
import org.eclipse.tm.te.tcf.filesystem.internal.preferences.TargetExplorerPreferencePage;
import org.eclipse.tm.te.tcf.filesystem.model.FSTreeNode;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.XMLMemento;
/**
* A facility class to load and save persistent data such including resolved content types, file's
* properties, and time stamps etc.
*/
public class PersistenceManager {
// The XML element of unresolvable.
private static final String ELEMENT_UNRESOLVABLE = "unresolvable"; //$NON-NLS-1$
// The root element of "unresolvables"
private static final String ELEMENT_UNRESOLVED = "unresolved"; //$NON-NLS-1$
// The attribute "contentType" to specify the content type id of the file.
private static final String ATTR_CONTENT_TYPE = "contentType"; //$NON-NLS-1$
// The XML element of resolvable.
private static final String ELEMENT_RESOLVABLE = "resolvable"; //$NON-NLS-1$
// The root element of "resolvables"
private static final String ELEMENT_RESOLVED = "resolved"; //$NON-NLS-1$
// The root element of the memento for content type resolving.
private static final String CONTENT_TYPE_ROOT = "contentTypes"; //$NON-NLS-1$
// The XML file name used to store the resolved content types.
private static final String CONTENT_TYPE_FILE = "contentTypes.xml"; //$NON-NLS-1$
// The attribute "value"
private static final String ATTR_VALUE = "value"; //$NON-NLS-1$
// The attribute "local name" of a qualified name.
private static final String ATTR_LOCAL_NAME = "localName"; //$NON-NLS-1$
// The attribute "qualifier" of a qualified name.
private static final String ATTR_QUALIFIER = "qualifier"; //$NON-NLS-1$
// The attribute of a node's URL
private static final String ATTR_URL = "URL"; //$NON-NLS-1$
// The element "property" to record a file's property
private static final String ELEMENT_PROPERTY = "property"; //$NON-NLS-1$
// The element "file" to specify a file's entry.
private static final String ELEMENT_FILE = "file"; //$NON-NLS-1$
// The root element of properties.
private static final String PERSISTENT_ROOT = "properties"; //$NON-NLS-1$
// Time stamp file used to persist the time stamps of each file.
private static final String TIMESTAMP_FILE = "timestamps.xml"; //$NON-NLS-1$
// The file used to store persistent properties of each file.
private static final String PERSISTENT_FILE = "persistent.xml"; //$NON-NLS-1$
// The singleton instance.
private static PersistenceManager instance;
// The time stamp for each file.
private Map<URL, Long> timestamps;
// The persistent properties of the files.
private Map<URL, Map<QualifiedName, String>> properties;
// Already known resolved content type of file nodes specified by their URLs.
private Map<URL, IContentType> resolved;
// Already known unresolvable file nodes specified by their URLs.
private Map<URL, URL> unresolved;
/**
* Get the singleton cache manager.
*
* @return The singleton cache manager.
*/
public static PersistenceManager getInstance() {
if (instance == null) {
instance = new PersistenceManager();
}
return instance;
}
/**
* Create a Persistent Manager instance.
*/
private PersistenceManager() {
loadTimestamps();
loadPersistentProperties();
loadContentTypes();
}
/**
* If the node is already considered unresolvable.
*
* @param node The file node.
* @return true if it is not resolvable or else false.
*/
public boolean isUnresovled(FSTreeNode node) {
return unresolved.get(node.getLocationURL()) != null;
}
/**
* Get the resolved content type of the node.
*
* @param node The file node.
* @return the content type of the node if it is resolvable or null.
*/
public IContentType getResolved(FSTreeNode node) {
return resolved.get(node.getLocationURL());
}
/**
* Add the node and its content type to the resolved list.
*
* @param node The file node.
* @param contentType Its content type.
*/
public void addResovled(FSTreeNode node, IContentType contentType) {
resolved.put(node.getLocationURL(), contentType);
}
/**
* Add the node as an unresolvable node.
*
* @param node The file node.
*/
public void addUnresolved(FSTreeNode node) {
unresolved.put(node.getLocationURL(), node.getLocationURL());
}
/**
* If the option of "autosaving" is set to on.
*
* @return true if it is auto saving or else false.
*/
public boolean isAutoSaving() {
IPreferenceStore preferenceStore = UIPlugin.getDefault().getPreferenceStore();
boolean autoSaving = preferenceStore
.getBoolean(TargetExplorerPreferencePage.PREF_AUTOSAVING);
return autoSaving;
}
/**
* Load the persistent properties from the persistent file in the cache's root directory.
*/
private void loadPersistentProperties() {
IMemento memento = readMemento(PERSISTENT_FILE, PERSISTENT_ROOT);
properties = Collections.synchronizedMap(new HashMap<URL, Map<QualifiedName, String>>());
IMemento[] children = memento.getChildren(ELEMENT_FILE);
if (children != null && children.length > 0) {
for (IMemento child : children) {
try {
String str = child.getString(ATTR_URL);
URL url = new URL(str);
Map<QualifiedName, String> nodeProperties = loadFileProperties(child);
properties.put(url, nodeProperties);
}
catch (MalformedURLException e) {
}
}
}
}
/**
* Load the content type information from the content type file.
*/
private void loadContentTypes() {
IMemento memento = readMemento(CONTENT_TYPE_FILE, CONTENT_TYPE_ROOT);
resolved = Collections.synchronizedMap(new HashMap<URL, IContentType>());
unresolved = Collections.synchronizedMap(new HashMap<URL, URL>());
IMemento mResolved = memento.getChild(ELEMENT_RESOLVED);
if (mResolved != null) {
IMemento[] children = mResolved.getChildren(ELEMENT_RESOLVABLE);
if (children != null && children.length > 0) {
for (IMemento child : children) {
try {
String str = child.getString(ATTR_URL);
URL url = new URL(str);
String id = child.getString(ATTR_CONTENT_TYPE);
IContentType contentType = Platform.getContentTypeManager()
.getContentType(id);
if (contentType != null) {
resolved.put(url, contentType);
}
}
catch (MalformedURLException e) {
}
}
}
}
IMemento mUnresolved = memento.getChild(ELEMENT_UNRESOLVED);
if (mUnresolved != null) {
IMemento[] children = mUnresolved.getChildren(ELEMENT_UNRESOLVABLE);
if (children != null && children.length > 0) {
for (IMemento child : children) {
try {
String str = child.getString(ATTR_URL);
URL url = new URL(str);
unresolved.put(url, url);
}
catch (MalformedURLException e) {
}
}
}
}
}
/**
* Save the content type information to the content type file.
*/
private void saveContentTypes() {
XMLMemento memento = XMLMemento.createWriteRoot(CONTENT_TYPE_ROOT);
IMemento mResolved = memento.createChild(ELEMENT_RESOLVED);
for (URL key : resolved.keySet()) {
IContentType iContentType = resolved.get(key);
IMemento mResolvable = mResolved.createChild(ELEMENT_RESOLVABLE);
mResolvable.putString(ATTR_URL, key.toString());
mResolvable.putString(ATTR_CONTENT_TYPE, iContentType.getId());
}
IMemento mUnresolved = memento.createChild(ELEMENT_UNRESOLVED);
for (URL key : unresolved.keySet()) {
IMemento mUnresolvable = mUnresolved.createChild(ELEMENT_UNRESOLVABLE);
mUnresolvable.putString(ATTR_URL, key.toString());
}
writeMemento(memento, CONTENT_TYPE_FILE);
}
/**
* Load a file's properties from the memento node.
*
* @param memento The memento node.
* @return The properties as a map.
*/
private Map<QualifiedName, String> loadFileProperties(IMemento memento) {
Map<QualifiedName, String> properties = Collections
.synchronizedMap(new HashMap<QualifiedName, String>());
IMemento[] children = memento.getChildren(ELEMENT_PROPERTY);
if (children != null && children.length > 0) {
for (IMemento child : children) {
String qualifier = child.getString(ATTR_QUALIFIER);
String localName = child.getString(ATTR_LOCAL_NAME);
QualifiedName name = new QualifiedName(qualifier, localName);
String value = child.getString(ATTR_VALUE);
properties.put(name, value);
}
}
return properties;
}
/**
* Read the memento from a memento file using the specified root element name.
*
* @param mementoFile The memento file.
* @param mementoRoot The memento's root element name.
* @return A memento of this file or an empty memento if the file does not exist.
*/
private IMemento readMemento(String mementoFile, String mementoRoot) {
File location = CacheManager.getInstance().getCacheRoot();
File stateFile = new File(location, mementoFile);
if (stateFile.exists()) {
BufferedReader reader = null;
try {
FileInputStream input = new FileInputStream(stateFile);
reader = new BufferedReader(new InputStreamReader(input, "utf-8")); //$NON-NLS-1$
IMemento memento = XMLMemento.createReadRoot(reader);
return memento;
}
catch (IOException e) {
}
catch (WorkbenchException e) {
}
finally {
if (reader != null) {
try {
reader.close();
}
catch (Exception e) {
}
}
}
}
return XMLMemento.createWriteRoot(mementoRoot);
}
/**
* Save the time stamps to the persistent file.
*/
private void savePersistentProperties() {
XMLMemento memento = XMLMemento.createWriteRoot(PERSISTENT_ROOT);
for (URL key : properties.keySet()) {
Map<QualifiedName, String> nodeProperties = properties.get(key);
if (!nodeProperties.keySet().isEmpty()) {
IMemento mFile = memento.createChild(ELEMENT_FILE);
mFile.putString(ATTR_URL, key.toString());
saveFileProperties(mFile, nodeProperties);
}
}
writeMemento(memento, PERSISTENT_FILE);
}
/**
* Save the file's properties to a memento.
*
* @param memento The memento object.
* @param properties The file properties.
*/
private void saveFileProperties(IMemento memento, Map<QualifiedName, String> properties) {
for (QualifiedName name : properties.keySet()) {
IMemento mProperty = memento.createChild(ELEMENT_PROPERTY);
mProperty.putString(ATTR_QUALIFIER, name.getQualifier());
mProperty.putString(ATTR_LOCAL_NAME, name.getLocalName());
mProperty.putString(ATTR_VALUE, properties.get(name));
}
}
/**
* Write the memento to a memento file.
*
* @param memento The memento object.
* @param mementoFile The file to write to.
*/
private void writeMemento(XMLMemento memento, String mementoFile) {
OutputStreamWriter writer = null;
try {
File location = CacheManager.getInstance().getCacheRoot();
File stateFile = new File(location, mementoFile);
FileOutputStream stream = new FileOutputStream(stateFile);
writer = new OutputStreamWriter(stream, "utf-8"); //$NON-NLS-1$
memento.save(writer);
}
catch (IOException e) {
e.printStackTrace();
}
finally {
if (writer != null) {
try {
writer.close();
}
catch (IOException e) {
}
}
}
}
/**
* Get the file properties of the specified node from the properties map.
*
* @param node The file node.
* @return The file properties object or empty properties object if it does not exist.
*/
public Map<QualifiedName, String> getPersistentProperties(FSTreeNode node) {
Map<QualifiedName, String> nodeProperties = properties.get(node.getLocationURL());
if (nodeProperties == null) {
nodeProperties = Collections.synchronizedMap(new HashMap<QualifiedName, String>());
properties.put(node.getLocationURL(), nodeProperties);
}
return nodeProperties;
}
/**
* Load the time stamps from the time stamps file in the cache's root directory.
*/
private void loadTimestamps() {
timestamps = Collections.synchronizedMap(new HashMap<URL, Long>());
File location = CacheManager.getInstance().getCacheRoot();
File tsFile = new File(location, TIMESTAMP_FILE);
if (tsFile.exists()) {
Properties properties = new Properties();
InputStream input = null;
try {
input = new BufferedInputStream(new FileInputStream(tsFile));
properties.loadFromXML(input);
}
catch (IOException e) {
e.printStackTrace();
}
finally {
if (input != null) {
try {
input.close();
}
catch (IOException e) {
}
}
}
Enumeration<String> keys = (Enumeration<String>) properties.propertyNames();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
String value = properties.getProperty(key);
long timestamp = 0L;
try {
timestamp = Long.parseLong(value);
timestamps.put(new URL(key), Long.valueOf(timestamp));
}
catch (Exception nfe) {
}
}
}
}
/**
* Save the time stamps to the time stamps file.
*/
private void saveTimestamps() {
Properties properties = new Properties();
for (URL key : timestamps.keySet()) {
Long timestamp = timestamps.get(key);
properties.setProperty(key.toString(), timestamp.toString());
}
File location = CacheManager.getInstance().getCacheRoot();
File fTimestamp = new File(location, TIMESTAMP_FILE);
OutputStream output = null;
try {
output = new BufferedOutputStream(new FileOutputStream(fTimestamp));
properties.storeToXML(output, null);
}
catch (IOException e) {
e.printStackTrace();
}
finally {
if (output != null) {
try {
output.close();
}
catch (Exception e) {
}
}
}
}
/**
* Set the time stamp of the FSTreeNode with the specified location.
*
* @param url The FSTreeNode's location URL.
* @param timestamp The new base time stamp to be set.
*/
public void setBaseTimestamp(URL url, long timestamp) {
timestamps.put(url, Long.valueOf(timestamp));
}
/**
* Remove the time stamp entry with the specified URL.
*
* @param url The URL key.
*/
public void removeBaseTimestamp(URL url) {
timestamps.remove(url);
}
/**
* Get the time stamp of the FSTreeNode with the specified location.
*
* @param url The FSTreeNode's location URL.
* @return The FSTreeNode's base time stamp.
*/
public long getBaseTimestamp(URL url) {
Long timestamp = timestamps.get(url);
return timestamp == null ? 0L : timestamp.longValue();
}
/**
* Dispose the cache manager so that it has a chance to save the timestamps and the persistent
* properties.
*/
public void dispose() {
saveTimestamps();
savePersistentProperties();
saveContentTypes();
}
}