/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.debug.internal.core; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunchConfigurationType; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.ibm.icu.text.MessageFormat; /** * The information associated with a launch configuration handle. */ public class LaunchConfigurationInfo { /** * Constants for XML element names and attributes */ private static final String KEY = "key"; //$NON-NLS-1$ private static final String VALUE = "value"; //$NON-NLS-1$ private static final String SET_ENTRY = "setEntry"; //$NON-NLS-1$ private static final String LAUNCH_CONFIGURATION = "launchConfiguration"; //$NON-NLS-1$ private static final String MAP_ENTRY = "mapEntry"; //$NON-NLS-1$ private static final String LIST_ENTRY = "listEntry"; //$NON-NLS-1$ private static final String SET_ATTRIBUTE = "setAttribute"; //$NON-NLS-1$ private static final String MAP_ATTRIBUTE = "mapAttribute"; //$NON-NLS-1$ private static final String LIST_ATTRIBUTE = "listAttribute"; //$NON-NLS-1$ private static final String BOOLEAN_ATTRIBUTE = "booleanAttribute"; //$NON-NLS-1$ private static final String INT_ATTRIBUTE = "intAttribute"; //$NON-NLS-1$ private static final String STRING_ATTRIBUTE = "stringAttribute"; //$NON-NLS-1$ private static final String TYPE = "type"; //$NON-NLS-1$ /** * This configurations attribute table. Keys are <code>String</code>s and * values are one of <code>String</code>, <code>Integer</code>, * <code>Boolean</code>, <code>Set<String></code>, * <code>List<String></code>, or * <code>Map<String, String></code> */ private TreeMap<String, Object> fAttributes; /** * This launch configuration's type */ private ILaunchConfigurationType fType; /** * Whether running on Sun 1.4 VM - see bug 110215 */ private static boolean fgIsSun14x = false; static { String vendor = System.getProperty("java.vm.vendor"); //$NON-NLS-1$ if (vendor.startsWith("Sun Microsystems")) { //$NON-NLS-1$ String version = System.getProperty("java.vm.version"); //$NON-NLS-1$ if (version.startsWith("1.4")) { //$NON-NLS-1$ fgIsSun14x = true; } } } /** * Constructs a new empty info */ protected LaunchConfigurationInfo() { setAttributeTable(new TreeMap<String, Object>()); } /** * Returns this configuration's attribute table. * * @return attribute table */ private TreeMap<String, Object> getAttributeTable() { return fAttributes; } /** * Sets this configuration's attribute table. * * @param table * attribute table */ private void setAttributeTable(TreeMap<String, Object> table) { fAttributes = table; } /** * Sets the attributes in this info to those in the given map. * * @param map the {@link Map} of attributes to set */ protected void setAttributes(Map<String, ?> map) { if (map == null) { setAttributeTable(new TreeMap<String, Object>()); return; } setAttributeTable(new TreeMap<String, Object>(map)); } /** * Returns the <code>String</code> attribute with the given key or the * given default value if undefined. * @param key the attribute name * @param defaultValue the value to be returned if the given key does not exist in the attribute table * * @return attribute specified by given key or the defaultValue if undefined * @throws CoreException * if the attribute with the given key exists but is not a * <code>String</code> */ protected String getStringAttribute(String key, String defaultValue) throws CoreException { Object attr = getAttributeTable().get(key); if (attr != null) { if (attr instanceof String) { return (String)attr; } throw new DebugException( new Status( IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED, MessageFormat.format(DebugCoreMessages.LaunchConfigurationInfo_Attribute__0__is_not_of_type_java_lang_String__1, new Object[] { key }), null ) ); } return defaultValue; } /** * Returns the <code>int</code> attribute with the given key or the given * default value if undefined. * @param key the name of the attribute * @param defaultValue the default value to return if the key does not appear in the attribute table * * @return attribute specified by given key or the defaultValue if undefined * @throws CoreException * if the attribute with the given key exists but is not an * <code>int</code> */ protected int getIntAttribute(String key, int defaultValue) throws CoreException { Object attr = getAttributeTable().get(key); if (attr != null) { if (attr instanceof Integer) { return ((Integer)attr).intValue(); } throw new DebugException( new Status( IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED, MessageFormat.format(DebugCoreMessages.LaunchConfigurationInfo_Attribute__0__is_not_of_type_int__2, new Object[] { key }), null ) ); } return defaultValue; } /** * Returns the <code>boolean</code> attribute with the given key or the * given default value if undefined. * @param key the name of the attribute * @param defaultValue the default value to return if the key does not appear in the attribute table * * @return attribute specified by given key or the defaultValue if undefined * @throws CoreException * if the attribute with the given key exists but is not a * <code>boolean</code> */ protected boolean getBooleanAttribute(String key, boolean defaultValue) throws CoreException { Object attr = getAttributeTable().get(key); if (attr != null) { if (attr instanceof Boolean) { return ((Boolean)attr).booleanValue(); } throw new DebugException( new Status( IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED, MessageFormat.format(DebugCoreMessages.LaunchConfigurationInfo_Attribute__0__is_not_of_type_boolean__3, new Object[] { key }), null ) ); } return defaultValue; } /** * Returns the <code>java.util.List</code> attribute with the given key or * the given default value if undefined. * @param key the name of the attribute * @param defaultValue the default value to return if the key does not appear in the attribute table * * @return attribute specified by given key or the defaultValue if undefined * @throws CoreException * if the attribute with the given key exists but is not a * <code>java.util.List</code> */ @SuppressWarnings("unchecked") protected List<String> getListAttribute(String key, List<String> defaultValue) throws CoreException { Object attr = getAttributeTable().get(key); if (attr != null) { if (attr instanceof List) { return (List<String>) attr; } throw new DebugException( new Status( IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED, MessageFormat.format(DebugCoreMessages.LaunchConfigurationInfo_Attribute__0__is_not_of_type_java_util_List__1, new Object[] { key }), null ) ); } return defaultValue; } /** * Returns the <code>java.util.Set</code> attribute with the given key or * the given default value if undefined. * * @param key the name of the attribute * @param defaultValue the default value to return if the key does not exist * in the attribute table * * @return attribute specified by given key or the defaultValue if undefined * @throws CoreException if the attribute with the given key exists but is * not a <code>java.util.Set</code> * * @since 3.3 */ @SuppressWarnings("unchecked") protected Set<String> getSetAttribute(String key, Set<String> defaultValue) throws CoreException { Object attr = getAttributeTable().get(key); if (attr != null) { if (attr instanceof Set) { return (Set<String>) attr; } throw new DebugException( new Status( IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED, MessageFormat.format(DebugCoreMessages.LaunchConfigurationInfo_35, new Object[] { key }), null ) ); } return defaultValue; } /** * Returns the <code>java.util.Map</code> attribute with the given key or * the given default value if undefined. * @param key the name of the attribute * @param defaultValue the default value to return if the key does not exist in the attribute table * * @return attribute specified by given key or the defaultValue if undefined * @throws CoreException * if the attribute with the given key exists but is not a * <code>java.util.Map</code> */ @SuppressWarnings("unchecked") protected Map<String, String> getMapAttribute(String key, Map<String, String> defaultValue) throws CoreException { Object attr = getAttributeTable().get(key); if (attr != null) { if (attr instanceof Map) { return (Map<String, String>) attr; } throw new DebugException( new Status( IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED, MessageFormat.format(DebugCoreMessages.LaunchConfigurationInfo_Attribute__0__is_not_of_type_java_util_Map__1, new Object[] { key }), null ) ); } return defaultValue; } /** * Sets this configuration's type. * * @param type * launch configuration type */ protected void setType(ILaunchConfigurationType type) { fType = type; } /** * Returns this configuration's type. * * @return launch configuration type */ protected ILaunchConfigurationType getType() { return fType; } /** * Returns a copy of this info object * * @return copy of this info */ protected LaunchConfigurationInfo getCopy() { LaunchConfigurationInfo copy = new LaunchConfigurationInfo(); copy.setType(getType()); copy.setAttributeTable(getAttributes()); return copy; } /** * Returns a copy of this info's attribute map. * * @return a copy of this info's attribute map */ protected TreeMap<String, Object> getAttributes() { return new TreeMap<String, Object>(getAttributeTable()); } /** * Sets the given attribute to the given value. Only working copy's should * use this API. * * @param key * attribute key * @param value * attribute value */ protected void setAttribute(String key, Object value) { if (value == null) { getAttributeTable().remove(key); } else { getAttributeTable().put(key, value); } } /** * Returns the content of this info as XML * * @return the content of this info as XML * @throws CoreException * if a attribute has been set with a null key * @throws IOException * if an exception occurs creating the XML * @throws ParserConfigurationException * if an exception occurs creating the XML * @throws TransformerException * if an exception occurs creating the XML */ @SuppressWarnings("unchecked") protected String getAsXML() throws CoreException, IOException, ParserConfigurationException, TransformerException { Document doc = LaunchManager.getDocument(); Element configRootElement = doc.createElement(LAUNCH_CONFIGURATION); doc.appendChild(configRootElement); configRootElement.setAttribute(TYPE, getType().getIdentifier()); for (String key : getAttributeTable().keySet()) { if (key == null) { throw new DebugException( new Status( IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED, DebugCoreMessages.LaunchConfigurationInfo_36, null ) ); } Object value = getAttributeTable().get(key); if (value == null) { continue; } Element element = null; String valueString = null; if (value instanceof String) { valueString = (String)value; element = createKeyValueElement(doc, STRING_ATTRIBUTE, key, valueString); } else if (value instanceof Integer) { valueString = ((Integer)value).toString(); element = createKeyValueElement(doc, INT_ATTRIBUTE, key, valueString); } else if (value instanceof Boolean) { valueString = ((Boolean)value).toString(); element = createKeyValueElement(doc, BOOLEAN_ATTRIBUTE, key, valueString); } else if (value instanceof List) { element = createListElement(doc, LIST_ATTRIBUTE, key, (List<String>) value); } else if (value instanceof Map) { element = createMapElement(doc, MAP_ATTRIBUTE, key, (Map<String, String>) value); } else if(value instanceof Set) { element = createSetElement(doc, SET_ATTRIBUTE, key, (Set<String>) value); } configRootElement.appendChild(element); } return LaunchManager.serializeDocument(doc); } /** * Helper method that creates a 'key value' element of the specified type * with the specified attribute values. * @param doc the {@link Document} * @param elementType the {@link Element} type to create * @param key the {@link Element} key * @param value the {@link Element} value * @return the new {@link Element} */ protected Element createKeyValueElement(Document doc, String elementType, String key, String value) { Element element = doc.createElement(elementType); element.setAttribute(KEY, key); element.setAttribute(VALUE, value); return element; } /** * Creates a new <code>Element</code> for the specified * <code>java.util.List</code> * * @param doc the doc to add the element to * @param elementType the type of the element * @param listKey the key for the element * @param list the list to fill the new element with * @return the new element */ protected Element createListElement(Document doc, String elementType, String listKey, List<String> list) { Element listElement = doc.createElement(elementType); listElement.setAttribute(KEY, listKey); for (String value : list) { Element element = doc.createElement(LIST_ENTRY); element.setAttribute(VALUE, value); listElement.appendChild(element); } return listElement; } /** * Creates a new <code>Element</code> for the specified * <code>java.util.Set</code> * * @param doc the doc to add the element to * @param elementType the type of the element * @param setKey the key for the element * @param set the set to fill the new element with * @return the new element * * @since 3.3 */ protected Element createSetElement(Document doc, String elementType, String setKey, Set<String> set) { Element setElement = doc.createElement(elementType); setElement.setAttribute(KEY, setKey); // persist in sorted order List<String> list = new ArrayList<String>(set); Collections.sort(list); Element element = null; for (String str : list) { element = doc.createElement(SET_ENTRY); element.setAttribute(VALUE, str); setElement.appendChild(element); } return setElement; } /** * Creates a new <code>Element</code> for the specified * <code>java.util.Map</code> * * @param doc the doc to add the element to * @param elementType the type of the element * @param mapKey the key for the element * @param map the map to fill the new element with * @return the new element * */ protected Element createMapElement(Document doc, String elementType, String mapKey, Map<String, String> map) { Element mapElement = doc.createElement(elementType); mapElement.setAttribute(KEY, mapKey); // persist in sorted order based on keys List<String> keys = new ArrayList<String>(map.keySet()); Collections.sort(keys); for (String key : keys) { String value = map.get(key); Element element = doc.createElement(MAP_ENTRY); element.setAttribute(KEY, key); element.setAttribute(VALUE, value); mapElement.appendChild(element); } return mapElement; } /** * Initializes the mapping of attributes from the XML file * @param root the root node from the XML document * @throws CoreException if a problem is encountered */ protected void initializeFromXML(Element root) throws CoreException { if (!root.getNodeName().equalsIgnoreCase(LAUNCH_CONFIGURATION)) { throw getInvalidFormatDebugException(); } // read type String id = root.getAttribute(TYPE); if (id == null) { throw getInvalidFormatDebugException(); } ILaunchConfigurationType type = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurationType(id); if (type == null) { String message= MessageFormat.format(DebugCoreMessages.LaunchConfigurationInfo_missing_type, new Object[]{id}); throw new DebugException( new Status( IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.MISSING_LAUNCH_CONFIGURATION_TYPE, message, null) ); } setType(type); NodeList list = root.getChildNodes(); Node node = null; Element element = null; String nodeName = null; for (int i = 0; i < list.getLength(); ++i) { node = list.item(i); short nodeType = node.getNodeType(); if (nodeType == Node.ELEMENT_NODE) { element = (Element) node; nodeName = element.getNodeName(); if (nodeName.equalsIgnoreCase(STRING_ATTRIBUTE)) { setStringAttribute(element); } else if (nodeName.equalsIgnoreCase(INT_ATTRIBUTE)) { setIntegerAttribute(element); } else if (nodeName.equalsIgnoreCase(BOOLEAN_ATTRIBUTE)) { setBooleanAttribute(element); } else if (nodeName.equalsIgnoreCase(LIST_ATTRIBUTE)) { setListAttribute(element); } else if (nodeName.equalsIgnoreCase(MAP_ATTRIBUTE)) { setMapAttribute(element); } else if(nodeName.equalsIgnoreCase(SET_ATTRIBUTE)) { setSetAttribute(element); } } } } /** * Loads a <code>String</code> from the specified element into the local attribute mapping * @param element the element to load from * @throws CoreException if a problem is encountered */ protected void setStringAttribute(Element element) throws CoreException { setAttribute(getKeyAttribute(element), getValueAttribute(element)); } /** * Loads an <code>Integer</code> from the specified element into the local attribute mapping * @param element the element to load from * @throws CoreException if a problem is encountered */ protected void setIntegerAttribute(Element element) throws CoreException { setAttribute(getKeyAttribute(element), Integer.valueOf(getValueAttribute(element))); } /** * Loads a <code>Boolean</code> from the specified element into the local attribute mapping * @param element the element to load from * @throws CoreException if a problem is encountered */ protected void setBooleanAttribute(Element element) throws CoreException { setAttribute(getKeyAttribute(element), Boolean.valueOf(getValueAttribute(element))); } /** * Reads a <code>List</code> attribute from the specified XML node and * loads it into the mapping of attributes * * @param element the element to read the list attribute from * @throws CoreException if the element has an invalid format */ protected void setListAttribute(Element element) throws CoreException { String listKey = element.getAttribute(KEY); NodeList nodeList = element.getChildNodes(); int entryCount = nodeList.getLength(); List<String> list = new ArrayList<String>(entryCount); Node node = null; Element selement = null; for (int i = 0; i < entryCount; i++) { node = nodeList.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { selement = (Element) node; if (!selement.getNodeName().equalsIgnoreCase(LIST_ENTRY)) { throw getInvalidFormatDebugException(); } list.add(getValueAttribute(selement)); } } setAttribute(listKey, list); } /** * Reads a <code>Set</code> attribute from the specified XML node and * loads it into the mapping of attributes * * @param element the element to read the set attribute from * @throws CoreException if the element has an invalid format * * @since 3.3 */ protected void setSetAttribute(Element element) throws CoreException { String setKey = element.getAttribute(KEY); NodeList nodeList = element.getChildNodes(); int entryCount = nodeList.getLength(); Set<String> set = new HashSet<String>(entryCount); Node node = null; Element selement = null; for(int i = 0; i < entryCount; i++) { node = nodeList.item(i); if(node.getNodeType() == Node.ELEMENT_NODE) { selement = (Element)node; if(!selement.getNodeName().equalsIgnoreCase(SET_ENTRY)) { throw getInvalidFormatDebugException(); } set.add(getValueAttribute(selement)); } } setAttribute(setKey, set); } /** * Reads a <code>Map</code> attribute from the specified XML node and * loads it into the mapping of attributes * * @param element the element to read the map attribute from * @throws CoreException if the element has an invalid format */ protected void setMapAttribute(Element element) throws CoreException { String mapKey = element.getAttribute(KEY); NodeList nodeList = element.getChildNodes(); int entryCount = nodeList.getLength(); Map<String, String> map = new HashMap<String, String>(entryCount); Node node = null; Element selement = null; for (int i = 0; i < entryCount; i++) { node = nodeList.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { selement = (Element) node; if (!selement.getNodeName().equalsIgnoreCase(MAP_ENTRY)) { throw getInvalidFormatDebugException(); } map.put(getKeyAttribute(selement), getValueAttribute(selement)); } } setAttribute(mapKey, map); } /** * Returns the <code>String</code> representation of the 'key' attribute from the specified element * @param element the element to read from * @return the value * @throws CoreException if a problem is encountered */ protected String getKeyAttribute(Element element) throws CoreException { String key = element.getAttribute(KEY); if (key == null) { throw getInvalidFormatDebugException(); } return key; } /** * Returns the <code>String</code> representation of the 'value' attribute from the specified element * @param element the element to read from * @return the value * @throws CoreException if a problem is encountered */ protected String getValueAttribute(Element element) throws CoreException { String value = element.getAttribute(VALUE); if (value == null) { throw getInvalidFormatDebugException(); } return value; } /** * Returns an invalid format exception for reuse * @return an invalid format exception */ protected DebugException getInvalidFormatDebugException() { return new DebugException( new Status( IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED, DebugCoreMessages.LaunchConfigurationInfo_Invalid_launch_configuration_XML__10, null ) ); } /** * Two <code>LaunchConfigurationInfo</code> objects are equal if and only * if they have the same type and they have the same set of attributes with * the same values. * * @see Object#equals(Object) */ @Override public boolean equals(Object obj) { // Make sure it's a LaunchConfigurationInfo object if (!(obj instanceof LaunchConfigurationInfo)) { return false; } // Make sure the types are the same LaunchConfigurationInfo other = (LaunchConfigurationInfo) obj; if (!fType.getIdentifier().equals(other.getType().getIdentifier())) { return false; } // Make sure the attributes are the same return compareAttributes(fAttributes, other.getAttributeTable()); } /** * Returns whether the two attribute maps are equal, consulting registered * comparator extensions. * * @param map1 attribute map * @param map2 attribute map * @return whether the two attribute maps are equal */ protected boolean compareAttributes(TreeMap<String, Object> map1, TreeMap<String, Object> map2) { LaunchManager manager = (LaunchManager)DebugPlugin.getDefault().getLaunchManager(); if (map1.size() == map2.size()) { Iterator<String> attributes = map1.keySet().iterator(); while (attributes.hasNext()) { String key = attributes.next(); Object attr1 = map1.get(key); Object attr2 = map2.get(key); if (attr2 == null) { return false; } Comparator<Object> comp = manager.getComparator(key); if (comp == null) { if (fgIsSun14x) { if(attr2 instanceof String & attr1 instanceof String) { // this is a hack for bug 110215, on SUN 1.4.x, \r // is stripped off when the stream is written to the // DOM // this is not the case for 1.5.x, so to be safe we // are stripping \r off all strings before we // compare for equality attr1 = ((String)attr1).replaceAll("\\r", ""); //$NON-NLS-1$ //$NON-NLS-2$ attr2 = ((String)attr2).replaceAll("\\r", ""); //$NON-NLS-1$ //$NON-NLS-2$ } } if (!attr1.equals(attr2)) { return false; } } else { if (comp.compare(attr1, attr2) != 0) { return false; } } } return true; } return false; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return fType.hashCode() + fAttributes.size(); } /** * Returns if the attribute map contains the specified key * @param attributeName the name of the attribute to check for * @return true if the attribute map contains the specified key, false otherwise * * @since 3.4.0 */ protected boolean hasAttribute(String attributeName) { return fAttributes.containsKey(attributeName); } /** * Removes the specified attribute from the mapping and returns * its value, or <code>null</code> if none. Does nothing * if the attribute name is <code>null</code> * @param attributeName the name of the attribute to remove * @return attribute value or <code>null</code> * * @since 3.4.0 */ protected Object removeAttribute(String attributeName) { if(attributeName != null) { return fAttributes.remove(attributeName); } return null; } }