001    package org.hackystat.sensor.xmldata.util;
002    
003    import java.util.ArrayList;
004    import java.util.Iterator;
005    import java.util.List;
006    import java.util.Set;
007    import java.util.concurrent.ConcurrentHashMap;
008    
009    /**
010     * Provides a thread-safe property map implementation for use as the value of
011     * the default 'pMap' field in all Sensor Data. Features of this abstract data
012     * type include:
013     * <ul>
014     * <li> Accepts only Strings as keys and values.
015     * <li> The entire property map can be encoded into a string for SOAP
016     * transmission.
017     * <li> The encoded string can be provided to a constructor to rebuild the
018     * object.
019     * <li> The puts and gets are thread-safe.
020     * </ul>
021     * @author Philip M. Johnson
022     * @version $Id: SensorDataPropertyMap.java,v 1.1.1.1 2005/10/20 23:56:44
023     * johnson Exp $
024     */
025    public class SensorDataPropertyMap {
026    
027      /** A thread-safe map holding the string property names and values. */
028      private ConcurrentHashMap<String, String> propertyMap = new ConcurrentHashMap<String, String>();
029    
030      /**
031       * The default public constructor for the SensorDataPropertyMap.
032       */
033      public SensorDataPropertyMap() {
034        // do nothing.
035      }
036    
037      /** The default empty SensorDataPropertyMap String representation. * */
038      private static String defaultMapString = new SensorDataPropertyMap().encode();
039    
040      /**
041       * Creates a thread-safe plist, initializing it with the contents of
042       * encodedMap.
043       * @param encodedMap A string produced from the encode() method.
044       * @throws Exception If encodedMap is not a legal encoded
045       * SensorDataPropertyMap.
046       */
047      public SensorDataPropertyMap(String encodedMap) throws Exception {
048        try {
049          List<String> propertyList = StringListCodec.decode(encodedMap);
050          for (Iterator<String> i = propertyList.iterator(); i.hasNext();) {
051            String propertyName = i.next();
052            String propertyValue = i.next();
053            propertyMap.put(propertyName, propertyValue);
054          }
055        }
056        catch (Exception e) {
057          throw new Exception("Error constructing SensorDataPropertyMap", e);
058        }
059      }
060    
061      /**
062       * Puts the (name, value) pair into the SensorDataPropertyMap.
063       * @param name The property name string.
064       * @param value The property value string.
065       */
066      public void put(String name, String value) {
067        this.propertyMap.put(name, value);
068      }
069    
070      /**
071       * Gets the property value associated with name, or returns null if not found.
072       * @param name The property name whose value is to be retrieved (if
073       * available).
074       * @return The property value associated with name, or null if not found.
075       */
076      public String get(String name) {
077        return this.propertyMap.get(name);
078      }
079    
080      /**
081       * Returns the property value associated with name, where name is not
082       * case-sensitive.
083       * @param name The case-insensitive property name whose value is to be
084       * retrieved.
085       * @return The property value, or null if no case-insensitive key (name) is
086       * found.
087       */
088      public String getIgnoreCase(String name) {
089        // Property maps should not have large numbers of elements, so iteration
090        // should be OK.
091        for (String key : this.propertyMap.keySet()) {
092          if (key.equalsIgnoreCase(name)) {
093            return this.propertyMap.get(key);
094          }
095        }
096        return null;
097      }
098    
099      /**
100       * Returns a string containing the "runTime" attribute, or null if not found.
101       * Since there has been confusion involving the spelling of this attribute,
102       * this method tries both "runTime" and "runtime". If both attributes are
103       * present (almost surely an error), the value of the runTime version is
104       * returned.
105       * @return A string containing the runTime (or runtime) attribute.
106       */
107      public String getRunTime() {
108        String runTime = this.propertyMap.get("runTime");
109        if (runTime == null) {
110          runTime = this.propertyMap.get("runtime");
111        }
112        return runTime;
113      }
114    
115      /**
116       * Returns the property value associated with name, or defaultValue if not
117       * found.
118       * @param name The property name whose value is to be retrieved.
119       * @param defaultValue The defaultValue to be returned if not present.
120       * @return The property value associated with name, or defaultValue if not
121       * found.
122       */
123      public String get(String name, String defaultValue) {
124        String value = get(name);
125        return (value == null) ? defaultValue : value;
126      }
127    
128      /**
129       * Encodes the contents of this PropertyMap into a String that can be
130       * persisted or transmitted using Soap. Throws a RuntimeException if an error
131       * occurs during encoding, which should be extremely rare.
132       * @return An encoded string.
133       */
134      public String encode() {
135        ArrayList<String> propertyList = new ArrayList<String>(this.propertyMap.size() * 2);
136        for (String name : this.propertyMap.keySet()) {
137          String value = this.propertyMap.get(name);
138          propertyList.add(name);
139          propertyList.add(value);
140        }
141        String encodedString;
142        try {
143          encodedString = StringListCodec.encode(propertyList);
144        }
145        catch (Exception e) {
146          throw new RuntimeException("Problems encoding the property map string", e);
147        }
148        return encodedString;
149      }
150    
151      /**
152       * Returns the contents of the property map in its encoded form. This is
153       * required for sensor data transmission to occur correctly.
154       * @return The contents of the property map in its encoded form.
155       */
156      @Override
157      public String toString() {
158        // return this.propertyMap.toString();
159        return this.encode();
160      }
161    
162      /**
163       * Returns the contents of the property map in human readable form.
164       * @return The property map in human readable form.
165       */
166      public String formattedString() {
167        return this.propertyMap.toString();
168      }
169    
170      /**
171       * Returns a set containing the keys associated with this property map.
172       * @return The keyset.
173       */
174      public Set<String> keySet() {
175        return this.propertyMap.keySet();
176      }
177    
178      /**
179       * Returns a new SensorDataPropertyMap instance generated from the encoded
180       * String. This method supplied for use as the Converter method in the SDT
181       * definition.
182       * @param encodedMap The encoded version of a SensorDataPropertyMap.
183       * @return A new SensorDataPropertyMap instance.
184       * @throws Exception If problems occur decoding the encoded string.
185       */
186      public static SensorDataPropertyMap getMap(String encodedMap) throws Exception {
187        return new SensorDataPropertyMap(encodedMap);
188      }
189    
190      /**
191       * Returns a new String representation of an empty SensorDataPropertyMap
192       * instance. This method supplied for use as the Defaulter method in the SDT
193       * definition.
194       * @return A new String encoding of an empty SensorDataPropertyMap instance.
195       */
196      public static String getDefaultMapString() {
197        return defaultMapString;
198      }
199    
200    }