/******************************************************************************* * Copyright (c) 2011, 2015 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.properties; import java.util.Collections; import java.util.EventObject; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.PlatformObject; import org.eclipse.tcf.te.runtime.activator.CoreBundleActivator; import org.eclipse.tcf.te.runtime.events.ChangeEvent; import org.eclipse.tcf.te.runtime.events.EventManager; import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer; import org.eclipse.tcf.te.runtime.interfaces.tracing.ITraceIds; /** * A generic properties container implementation. * <p> * <b>Note:</b> The properties container implementation is not thread-safe. Clients requiring * a thread-safe implementation should subclass the properties container and * overwrite {@link #checkThreadAccess()}. */ public class PropertiesContainer extends PlatformObject implements IPropertiesContainer { // Used to have a simple check that the random generated UUID isn't // the same if objects of this type are created very rapidly. private volatile static UUID LAST_UUID_GENERATED = null; // The unique node id private final UUID uniqueId; // The flag to remember the notification enablement private boolean changeEventsEnabled = false; /** * The custom properties map. The keys are always strings, the value might be any object. */ private Map<String, Object> properties = new HashMap<String, Object>(); /** * Constructor. */ public PropertiesContainer() { super(); Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ // Initialize the unique node id. UUID uuid = UUID.randomUUID(); while (LAST_UUID_GENERATED != null && LAST_UUID_GENERATED.equals(uuid)) { uuid = UUID.randomUUID(); } LAST_UUID_GENERATED = uuid; uniqueId = LAST_UUID_GENERATED; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#getUUID() */ @Override public final UUID getUUID() { return uniqueId; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { boolean equals = super.equals(obj); if (!equals && obj instanceof PropertiesContainer) { return uniqueId.equals(((PropertiesContainer)obj).uniqueId); } return equals; } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return uniqueId.hashCode(); } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append("UUID=" + uniqueId.toString()); //$NON-NLS-1$ // print the first level of the properties map only buffer.append(", properties={"); //$NON-NLS-1$ for (String key : properties.keySet()) { buffer.append(key); buffer.append("="); //$NON-NLS-1$ Object value = properties.get(key); if (value instanceof Map || value instanceof IPropertiesContainer) { buffer.append("{...}"); //$NON-NLS-1$ } else { buffer.append(value); } buffer.append(", "); //$NON-NLS-1$ } if (buffer.toString().endsWith(", ")) { //$NON-NLS-1$ buffer.deleteCharAt(buffer.length() - 1); buffer.deleteCharAt(buffer.length() - 1); } buffer.append("}"); //$NON-NLS-1$ return buffer.toString(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.nodes.IPropertiesContainer#setChangeEventsEnabled(boolean) */ @Override public final boolean setChangeEventsEnabled(boolean enabled) { boolean changed = changeEventsEnabled != enabled; if (changed) changeEventsEnabled = enabled; return changed; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.nodes.IPropertiesContainer#changeEventsEnabled() */ @Override public final boolean changeEventsEnabled() { return changeEventsEnabled; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer#fireChangeEvent(java.lang.String, java.lang.Object, java.lang.Object) */ @Override public void fireChangeEvent(String key, Object oldValue, Object newValue) { Assert.isNotNull(key); EventObject event = newEvent(this, key, oldValue, newValue); if (event != null) EventManager.getInstance().fireEvent(event); } /** * Creates a new property change notification event object. The event object is initialized * with the given parameter. * <p> * This method is typically called from {@link #fireChangeEvent(String, Object, Object)}. * <code>Null</code> is returned if no event should be fired. * * @param source The source object. Must not be <code>null</code>. * @param key The property key. Must not be <code>null</code>. * @param oldValue The old properties value. * @param newValue The new properties value. * * @return The new property change notification event instance or <code>null</code>. */ private final EventObject newEvent(Object source, String key, Object oldValue, Object newValue) { Assert.isNotNull(source); Assert.isNotNull(key); // Check if the event is dropped if (dropEvent(source, key, oldValue, newValue)) { // Log the event dropping if tracing is enabled if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_EVENTS)) { CoreBundleActivator.getTraceHandler().trace("Drop event notification (not created change event)\n\t\t" + //$NON-NLS-1$ "for eventId = " + key + ",\n\t\t" + //$NON-NLS-1$ //$NON-NLS-2$ "currentValue = " + oldValue + ",\n\t\t" + //$NON-NLS-1$ //$NON-NLS-2$ "newValue = " + newValue, //$NON-NLS-1$ 0, ITraceIds.TRACE_EVENTS, IStatus.WARNING, this); } return null; } // Create the notification event instance. return newEventDelegate(source, key, oldValue, newValue); } /** * Creates a new property change notification event object instance. * <p> * This method is typically called from {@link #newEvent(Object, String, Object, Object)} * if notifications are enabled. * * @param source The source object. Must not be <code>null</code>. * @param key The property key. Must not be <code>null</code>. * @param oldValue The old properties value. * @param newValue The new properties value. * * @return The new property change notification event instance or <code>null</code>. */ protected EventObject newEventDelegate(Object source, String key, Object oldValue, Object newValue) { Assert.isNotNull(source); Assert.isNotNull(key); return new ChangeEvent(source, key, oldValue, newValue); } /** * Returns if or if not notifying the given property change has to be dropped. * * @param source The source object. Must not be <code>null</code>. * @param key The property key. Must not be <code>null</code>. * @param oldValue The old properties value. * @param newValue The new properties value. * * @return <code>True</code> if dropping the property change notification, <code>false</code> if notifying the property change. */ protected boolean dropEvent(Object source, String key, Object oldValue, Object newValue) { Assert.isNotNull(source); Assert.isNotNull(key); return !changeEventsEnabled || key.endsWith(SILENT_PROPERTY); } /** * Checks if the access to the properties container happens in * a privileged thread. * <p> * The default implementation returns always <code>true</code>. Overwrite * to implement thread-safe properties container access. * * @return <code>True</code> if the access to the properties container is allowed, <code>false</code> otherwise. */ protected boolean checkThreadAccess() { return true; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#getProperties() */ @Override public Map<String, Object> getProperties() { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ return Collections.unmodifiableMap(new HashMap<String, Object>(properties)); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#getProperty(java.lang.String) */ @Override public Object getProperty(String key) { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ return properties.get(key); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#getBooleanProperty(java.lang.String) */ @Override public final boolean getBooleanProperty(String key) { Object value = getProperty(key); if (value instanceof Boolean) { return ((Boolean)value).booleanValue(); } if (value instanceof String) { String val = ((String)value).trim(); return "TRUE".equalsIgnoreCase(val) || "1".equals(val) || "Y".equalsIgnoreCase(val) || //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ "JA".equalsIgnoreCase(val) || "YES".equalsIgnoreCase(val); //$NON-NLS-1$ //$NON-NLS-2$ } return false; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#getLongProperty(java.lang.String) */ @Override public final long getLongProperty(String key) { Object value = getProperty(key); try { if (value instanceof Long) { return ((Long)value).longValue(); } if (value instanceof Number) { return ((Number)value).longValue(); } if (value != null) { return Long.decode(value.toString()).longValue(); } } catch (Exception e) { /* ignored on purpose */ } return -1; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#getIntProperty(java.lang.String) */ @Override public final int getIntProperty(String key) { Object value = getProperty(key); try { if (value instanceof Integer) { return ((Integer)value).intValue(); } if (value instanceof Number) { return ((Number)value).intValue(); } if (value != null) { return Integer.decode(value.toString()).intValue(); } } catch (Exception e) { /* ignored on purpose */ } return -1; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#getStringProperty(java.lang.String) */ @Override public final String getStringProperty(String key) { Object value = getProperty(key); return value instanceof String ? (String)value : (value != null ? value.toString() : null); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#getFloatProperty(java.lang.String) */ @Override public final float getFloatProperty(String key) { Object value = getProperty(key); try { if (value instanceof Float) { return ((Float)value).floatValue(); } if (value instanceof Number) { return ((Number)value).floatValue(); } if (value != null) { return Float.parseFloat(value.toString()); } } catch (Exception e) { /* ignored on purpose */ } return Float.NaN; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#getDoubleProperty(java.lang.String) */ @Override public final double getDoubleProperty(String key) { Object value = getProperty(key); try { if (value instanceof Double) { return ((Double)value).doubleValue(); } if (value instanceof Number) { return ((Number)value).doubleValue(); } if (value != null) { return Double.parseDouble(value.toString()); } } catch (Exception e) { /* ignored on purpose */ } return Double.NaN; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#setProperties(java.util.Map) */ @Override public void setProperties(Map<String, Object> properties) { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(properties); // Change the properties only if they have changed really if (this.properties.equals(properties)) { return; } // Clear out all old properties this.properties.clear(); // Apply everything from the given properties this.properties.putAll(properties); // And signal the change postSetProperties(properties); } /** * Method hook called by {@link #setProperties(Map)} just before the * method returns to the caller. * * @param properties The map of properties set. Must not be <code>null</code>. */ protected void postSetProperties(Map<String, ?> properties) { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(properties); fireChangeEvent("properties", null, properties); //$NON-NLS-1$ } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer#addProperties(java.util.Map) */ @Override public final void addProperties(Map<String, ?> properties) { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(properties); // Apply everything from the given properties this.properties.putAll(properties); // And signal the change postSetProperties(properties); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#setProperty(java.lang.String, boolean) */ @Override public final boolean setProperty(String key, boolean value) { boolean oldValue = getBooleanProperty(key); if (oldValue != value) { return setProperty(key, Boolean.valueOf(value)); } return false; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#setProperty(java.lang.String, long) */ @Override public final boolean setProperty(String key, long value) { long oldValue = getLongProperty(key); if (oldValue != value) { return setProperty(key, Long.valueOf(value)); } return false; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#setProperty(java.lang.String, int) */ @Override public final boolean setProperty(String key, int value) { int oldValue = getIntProperty(key); if (oldValue != value) { return setProperty(key, Integer.valueOf(value)); } return false; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#setProperty(java.lang.String, float) */ @Override public final boolean setProperty(String key, float value) { float oldValue = getFloatProperty(key); if (oldValue != value) { return setProperty(key, Float.valueOf(value)); } return false; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#setProperty(java.lang.String, double) */ @Override public final boolean setProperty(String key, double value) { double oldValue = getDoubleProperty(key); if (oldValue != value) { return setProperty(key, Double.valueOf(value)); } return false; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#setProperty(java.lang.String, java.lang.Object) */ @Override public boolean setProperty(String key, Object value) { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(key); Object oldValue = properties.get(key); if ((oldValue == null && value != null) || (oldValue != null && !oldValue.equals(value))) { if (value != null) { properties.put(key, value); } else { properties.remove(key); } postSetProperty(key, value, oldValue); return true; } return false; } /** * Method hook called by {@link #setProperty(String, Object)} in case the value * of the given property changed and just before the method returns to the caller. * * @param key The property key. Must not be <code>null</code>. * @param value The property value. * @param oldValue The old property value. */ public void postSetProperty(String key, Object value, Object oldValue) { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(key); fireChangeEvent(key, oldValue, value); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#clearProperties() */ @Override public final void clearProperties() { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ properties.clear(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer#isEmpty() */ @Override public boolean isEmpty() { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ return properties.isEmpty(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer#containsKey(java.lang.String) */ @Override public boolean containsKey(String key) { Assert.isTrue(checkThreadAccess(), "Illegal Thread Access"); //$NON-NLS-1$ Assert.isNotNull(key); return properties.containsKey(key); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#isProperty(java.lang.String, long) */ @Override public final boolean isProperty(String key, long value) { return getLongProperty(key) == value; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#isProperty(java.lang.String, boolean) */ @Override public final boolean isProperty(String key, boolean value) { return getBooleanProperty(key) == value; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#isProperty(java.lang.String, int) */ @Override public final boolean isProperty(String key, int value) { return getIntProperty(key) == value; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#isProperty(java.lang.String, float) */ @Override public final boolean isProperty(String key, float value) { return Math.abs(getFloatProperty(key) - value) < .0000001; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#isProperty(java.lang.String, double) */ @Override public final boolean isProperty(String key, double value) { return Math.abs(getDoubleProperty(key) - value) < .0000001; } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#isPropertyIgnoreCase(java.lang.String, java.lang.String) */ @Override public final boolean isPropertyIgnoreCase(String key, String value) { String property = getStringProperty(key); return (property == null && value == null) || (property != null && property.equalsIgnoreCase(value)); } /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.interfaces.IPropertiesContainer#isProperty(java.lang.String, java.lang.Object) */ @Override public final boolean isProperty(String key, Object value) { Object property = getProperty(key); return (property == null && value == null) || (property != null && property.equals(value)); } /* (non-Javadoc) * @see org.eclipse.core.runtime.PlatformObject#getAdapter(java.lang.Class) */ @Override public Object getAdapter(Class adapter) { Object adapted = super.getAdapter(adapter); return adapted != null ? adapted : Platform.getAdapterManager().loadAdapter(this, adapter.getName()); } }