/*******************************************************************************
* 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.launch.core.persistence.launchcontext;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.tcf.te.launch.core.lm.interfaces.ILaunchContextLaunchAttributes;
import org.eclipse.tcf.te.launch.core.lm.interfaces.ILaunchSpecification;
import org.eclipse.tcf.te.launch.core.persistence.AbstractItemListPersistenceDelegate;
import org.eclipse.tcf.te.launch.core.persistence.AbstractItemListXMLParser;
import org.eclipse.tcf.te.runtime.model.interfaces.IModelNode;
import org.eclipse.tcf.te.runtime.services.ServiceManager;
import org.eclipse.tcf.te.runtime.services.interfaces.IPropertiesAccessService;
/**
* Context selector persistence delegate.
*/
public class LaunchContextsPersistenceDelegate {
// The read cache for step contexts. Avoid running time consuming
// re-parsing of an already parsed step context description again and again.
private final static Map<String, List<IModelNode>> readCache = new LinkedHashMap<String, List<IModelNode>>();
// The write cache for target contexts. Avoids re-generating the XML again and again.
private final static Map<String, String> writeCache = new LinkedHashMap<String, String>();
// Limit the read cache to the last 10 read step contexts
private final static int READ_CACHE_MAX_CAPACITY = 25;
// Limit the write cache to the last 10 written step contexts
private final static int WRITE_CACHE_MAX_CAPACITY = 25;
private static final String TAG_LAUNCH_CONTEXT = "context"; //$NON-NLS-1$
private static final AbstractItemListPersistenceDelegate<IModelNode> delegate =
new AbstractItemListPersistenceDelegate<IModelNode>(TAG_LAUNCH_CONTEXT, ILaunchContextLaunchAttributes.ATTR_LAUNCH_CONTEXTS) {
@Override
protected AbstractItemListXMLParser<IModelNode> getXMLParser() {
return new AbstractItemListXMLParser<IModelNode>(TAG_LAUNCH_CONTEXT) {
@Override
protected Class<?> getReadClass() {
return IModelNode.class;
}
};
}
};
/**
* Saves the selected launch contexts to the specified launch configuration working copy. If the
* selected launch contexts are <code>null</code> or empty, the attribute will be removed from
* the specified launch configuration working copy.
*
* @param wc The launch configuration working copy. Must not be <code>null</code>.
* @param contexts The launch contexts to save or <code>null</code>.
*/
public final static void setLaunchContexts(ILaunchConfigurationWorkingCopy wc, IModelNode[] contexts) {
delegate.setItems(wc, contexts);
}
/**
* Saves the selected launch contexts to the specified launch specification. If the selected
* launch contexts are <code>null</code> or empty, the attribute will be removed from the
* specified launch specification.
*
* @param launchSpec The launch specification. Must not be <code>null</code>.
* @param contexts The launch contexts to save or <code>null</code>.
*/
public final static void setLaunchContexts(ILaunchSpecification launchSpec, IModelNode[] contexts) {
delegate.setItems(launchSpec, contexts);
}
/**
* Writes the given launch contexts into a string encoded in XML.
*
* @param contexts The launch contexts to encode. Must not be <code>null</code>.
* @return The full XML representation of the given contexts or <code>null</code>.
*/
public final static String encodeLaunchContexts(IModelNode[] contexts) {
Assert.isNotNull(contexts);
// The final result
String result = null;
// Generate the write cache key
String writeCacheKey = makeWriteCacheKey(contexts);
// Check if we have the contexts already generated before
synchronized (writeCache) {
if (writeCache.containsKey(writeCacheKey)) {
result = writeCache.get(writeCacheKey);
}
}
// If no cache hit, generate from scratch
if (result == null) {
result = delegate.encodeItems(contexts);
synchronized (writeCache) {
// Limit the write cache capacity
checkCacheCapacity(writeCache, WRITE_CACHE_MAX_CAPACITY);
// And put it into the write cache
writeCache.put(writeCacheKey, result);
}
}
return result;
}
/**
* Generates a write cache key from the given contexts.
*
* @param contexts The contexts
* @return The corresponding write key cache.
*/
private static String makeWriteCacheKey(IModelNode[] contexts) {
Assert.isNotNull(contexts);
StringBuffer key = new StringBuffer();
for (IModelNode context : contexts) {
key.append(Integer.toHexString(context.hashCode()));
key.append(':');
}
if (key.charAt(key.length() - 1) == ':') {
key.setCharAt(key.length() - 1, ' ');
}
return key.toString().trim();
}
/**
* Reads the selected launch contexts from the given XML encoded string.
*
* @param encodedContexts The selected launch contexts encoded as XML string. Must not be <code>null</code>.
* @return The selected launch contexts or an empty array.
*/
public final static IModelNode[] decodeLaunchContexts(String encodedContexts) {
Assert.isNotNull(encodedContexts);
List<IModelNode> contexts = null;
if (!"".equals(encodedContexts.trim())) { //$NON-NLS-1$
synchronized (readCache) {
// Check if we have the contexts already parsed before
if (readCache.containsKey(encodedContexts)) {
// Take the result from the cache
contexts = readCache.get(encodedContexts);
// check sanity. If empty or we cannot find the step context,
// drop the cache value and decode again.
ListIterator<IModelNode> iterator = contexts.listIterator();
while (iterator.hasNext()) {
IModelNode node = iterator.next();
IPropertiesAccessService service = ServiceManager.getInstance().getService(node, IPropertiesAccessService.class);
boolean isGhost = false;
if (service != null) {
Object value = service.getProperty(node, IModelNode.PROPERTY_IS_GHOST);
if (value instanceof Boolean) {
isGhost = ((Boolean)value).booleanValue();
}
}
if (isGhost) {
contexts = null;
readCache.remove(encodedContexts);
break;
}
}
if (contexts != null && contexts.isEmpty()) {
readCache.remove(encodedContexts);
contexts = null;
}
}
}
if (contexts == null || contexts.isEmpty()) {
contexts = delegate.decodeItems(encodedContexts);
if (!contexts.isEmpty()) {
synchronized (readCache) {
// Limit the read cache capacity
checkCacheCapacity(readCache, READ_CACHE_MAX_CAPACITY);
// Put the result into the read cache
readCache.put(encodedContexts, contexts);
}
}
}
}
return contexts != null ? contexts.toArray(new IModelNode[contexts.size()]) : new IModelNode[0];
}
/**
* Internal helper method to ensure a maximum capacity of the caches.
*/
private final static void checkCacheCapacity(Map<String, ?> cache, int maxCapacity) {
if (cache.size() < maxCapacity) {
return;
}
// Get all keys
String[] keys = cache.keySet().toArray(new String[cache.keySet().size()]);
// And remove all keys starting with the eldest till the
// capacity is fine again.
for (String key : keys) {
cache.remove(key);
if (cache.size() < maxCapacity / 2) {
break;
}
}
}
/**
* Returns the list of configured launch contexts from the given launch configuration.
* <p>
* If the given launch configuration is <code>null</code> and the method will return an empty
* array.
*
* @param configuration The launch configuration or <code>null</code>.
* @return The list of configured launch contexts or an empty array.
*/
public static final IModelNode[] getLaunchContexts(ILaunchConfiguration configuration) {
List<IModelNode> list = delegate.getItems(configuration);
return list.toArray(new IModelNode[list.size()]);
}
/**
* Returns the list of configured launch contexts from the given launch specification.
* <p>
* If the given launch specification is <code>null</code> and the method will return an empty
* array.
*
* @param launchSpec The launch specification or <code>null</code>.
* @return The list of configured launch contexts or an empty array.
*/
public static final IModelNode[] getLaunchContexts(ILaunchSpecification launchSpec) {
List<IModelNode> list = delegate.getItems(launchSpec);
return list.toArray(new IModelNode[list.size()]);
}
/**
* Returns the first configured launch context from the given launch configuration.
*
* @param configuration The launch configuration or <code>null</code>.
* @return The first configured launch context or <code>null</code>.
*/
public static final IModelNode getFirstLaunchContext(ILaunchConfiguration configuration) {
List<IModelNode> list = delegate.getItems(configuration);
return list != null && !list.isEmpty() ? list.get(0) : null;
}
}