/*******************************************************************************
* Copyright (c) 2007, 2008 Freescale Semiconductor 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:
* Freescale Semiconductor - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.debug.internal.core;
import java.io.IOException;
import java.io.StringReader;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.eclipse.cdt.debug.core.CDebugUtils;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.debug.internal.core.model.CDebugTarget;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.model.IDebugTarget;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Settings manager
*
* The settings manager stores a set of settings,
* (key/value) pairs in the launch configuration so they exist across debug sessions.
*
* All active settings are stored together in a single configuration entry
* (ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_FORMAT).
*
* Every setting is identified by a string identifier. That string identifier can be used to
* store an additional setting, to remove an exiting one or to retrieve a previously stored setting.
*
* The setting value consists of a String.
*
* Settings fade out automatically so clients do not necessarily need to delete old settings. This makes it
* possible to build the string identifiers with names out of the user application, like function names or
* variable names, without the danger of a constantly growing launch configuration.
* However it also causes that the settings manager must only be used for configurations and customizations for which
* always reasonable defaults exist.
*
* As cleanup policy the settings manager only keeps a certain number of settings and drops the
* least recently used one when more settings are added. The least recently used order is maintained
* across debug sessions.
*
*/
public class CSettingsManager {
/**
* the name of the XML node for the list
*/
private static final String CONTENT_LIST = "contentList"; //$NON-NLS-1$
/**
* the name of the XML node for every format entry
*/
private static final String CONTENT = "content"; //$NON-NLS-1$
/**
* the attribute name used to identify the object to store the content for.
*/
private static final String ATTR_CONTENT_ID = "id"; //$NON-NLS-1$
/**
* the attribute name of the actual content
*/
private static final String ATTR_CONTENT_VALUE = "val"; //$NON-NLS-1$
/**
* Number defining how many settings are stored.
* Whenever an additional setting is added when there are already MAX_USED_COUNT settings, the
* least recently used setting is dropped.
*
* The actual value is chosen to be high enough for normal use cases, but still low enough to avoid that the launch configuration
* gets arbitrarily large
*/
private static int MAX_ELEMENT_COUNT = 100;
/**
* the map used to actually store the format information
* as key String are used, values are of type String too.
*
* The map automatically is limited to MAX_ELEMENT_COUNT
* elements, dropping the least recently used one
* when more elements are added.
*/
private Map fContentMap = new LinkedHashMap(MAX_ELEMENT_COUNT, 0.75f, true) {
private static final long serialVersionUID = 1;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ELEMENT_COUNT;
}
};
/**
* the debug target we store the values for
*/
private CDebugTarget fDebugTarget;
/**
* Store the value for the given id.
* @param id used to identify the information. Different objects/topics should use different identifiers.
* @param value content to be stored
*/
public synchronized void putValue( String id, String value ) {
fContentMap.put(id, value);
}
/**
* remove the stored format for the given id.
* @param id used to identify the formatting information. Different objects/topics should use different identifiers.
*/
public synchronized void removeValue( String id ) {
fContentMap.remove( id );
}
/** Retrieve the value for the given id.
* @param id used to identify the formatting information. Different objects/topics should use different identifiers.
* @return returns the entry information for the given id, or null if no such information is available.
*/
public synchronized String getValue( String id ) {
String entry= (String) fContentMap.get( id );
return entry;
}
/** constructor.
* @param debugTarget
*/
public CSettingsManager( CDebugTarget debugTarget ) {
fDebugTarget = debugTarget;
initialize();
}
/** get the string format of the current content.
* Only stores entries which have been used in the last MAX_USED_COUNT debug sessions.
* @return
*/
private String getMemento() {
Document document = null;
try {
document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element node = document.createElement( CONTENT_LIST );
document.appendChild( node );
Set entrySet = fContentMap.entrySet();
Iterator it = entrySet.iterator();
while ( it.hasNext() ) {
Map.Entry entry= (Map.Entry) it.next();
String id= (String)entry.getKey();
String value= (String)entry.getValue();
Element child = document.createElement( CONTENT );
child.setAttribute( ATTR_CONTENT_ID, id );
child.setAttribute( ATTR_CONTENT_VALUE, value );
node.appendChild( child );
}
return CDebugUtils.serializeDocument( document, false );
}
catch( ParserConfigurationException e ) {
DebugPlugin.log( e );
}
catch( IOException e ) {
DebugPlugin.log( e );
}
catch( TransformerException e ) {
DebugPlugin.log( e );
}
return null;
}
/** set the current state to the one given by the memento.
* @param memento a string representation of the state to be loaded.
* @throws CoreException
*/
private void initializeFromMemento( String memento ) throws CoreException {
try {
fContentMap.clear();
DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
StringReader reader = new StringReader( memento );
InputSource source = new InputSource( reader );
Element root = parser.parse( source ).getDocumentElement();
if ( root.getNodeName().equalsIgnoreCase( CONTENT_LIST ) ) {
NodeList list = root.getChildNodes();
int i = list.getLength() - 1; // backwards to keep least recent access order.
for( ; i >= 0; i-- ) {
Node node = list.item( i );
short type = node.getNodeType();
if ( type == Node.ELEMENT_NODE ) {
Element elem = (Element)node;
if ( elem.getNodeName().equalsIgnoreCase( CONTENT ) ) {
String id = elem.getAttribute( ATTR_CONTENT_ID );
String value= elem.getAttribute( ATTR_CONTENT_VALUE );
if ( id == null || id.length() == 0 ) {
DebugPlugin.logMessage( "unexpected entry in CSettingsManager.initializeFromMemento", null ); //$NON-NLS-1$
continue;
}
putValue( id, value );
}
}
}
return;
}
DebugPlugin.logMessage( "unexpected content", null ); //$NON-NLS-1$
}
catch( ParserConfigurationException e ) {
DebugPlugin.log( e );
}
catch( SAXException e ) {
DebugPlugin.log( e );
}
catch( IOException e ) {
DebugPlugin.log( e );
}
}
/**
* read the stored format from the launch configuration
*/
private void initialize() {
ILaunchConfiguration config = getDebugTarget().getLaunch().getLaunchConfiguration();
try {
String memento = config.getAttribute( ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_FORMAT, "" ); //$NON-NLS-1$
if ( memento != null && memento.trim().length() != 0 )
initializeFromMemento( memento );
}
catch( CoreException e ) {
DebugPlugin.log( e );
}
}
/**
* store the current content in the launch configuration.
*/
public synchronized void save() {
ILaunchConfiguration config = getDebugTarget().getLaunch().getLaunchConfiguration();
try {
ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
wc.setAttribute( ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_FORMAT, getMemento() );
wc.doSave();
}
catch( CoreException e ) {
DebugPlugin.log( e );
}
}
/**
* accessor to the debug target
*/
IDebugTarget getDebugTarget() {
return fDebugTarget;
}
}