content: /**
   *  Licensed to the Apache Software Foundation (ASF) under one or more
   *  contributor license agreements.  See the NOTICE file distributed with
   *  this work for additional information regarding copyright ownership.
   *  The ASF licenses this file to You under the Apache License, Version 2.0
   *  (the "License"); you may not use this file except in compliance with
   *  the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   */
  package org.apache.aries.jmx.codec;
  
  import static org.apache.aries.jmx.util.TypeUtils.fromString;
  import static org.apache.aries.jmx.util.TypeUtils.primitiveTypes;
  import static org.apache.aries.jmx.util.TypeUtils.types;
  import static org.osgi.jmx.JmxConstants.ARRAY_OF;
  import static org.osgi.jmx.JmxConstants.KEY;
  import static org.osgi.jmx.JmxConstants.PROPERTY_TYPE;
  import static org.osgi.jmx.JmxConstants.P_BOOLEAN;
  import static org.osgi.jmx.JmxConstants.P_BYTE;
  import static org.osgi.jmx.JmxConstants.P_CHAR;
  import static org.osgi.jmx.JmxConstants.P_DOUBLE;
  import static org.osgi.jmx.JmxConstants.P_FLOAT;
  import static org.osgi.jmx.JmxConstants.P_INT;
  import static org.osgi.jmx.JmxConstants.P_LONG;
  import static org.osgi.jmx.JmxConstants.TYPE;
  import static org.osgi.jmx.JmxConstants.VALUE;
  import static org.osgi.jmx.JmxConstants.VECTOR_OF;
  
  import java.lang.reflect.Array;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.StringTokenizer;
  import java.util.Vector;
  import javax.management.openmbean.CompositeData;
  import javax.management.openmbean.CompositeDataSupport;
  import javax.management.openmbean.OpenDataException;
  
  import org.osgi.jmx.JmxConstants;
  
  /**
   * <p>
   * <tt>PropertyData</tt> represents Property Type @see {@link JmxConstants#PROPERTY_TYPE}. It is a codec for the
   * <code>CompositeData</code> representing a Property with an associated Type and Value.
   * </p>
   *
   * @version $Rev$ $Date$
   */
  public class PropertyData<T> {
  
      /**
       * @see JmxConstants#KEY_ITEM
       */
      private String key;
  
      /**
       * @see JmxConstants#SCALAR
       */
      private T value;
  
      /**
       * @see JmxConstants#VALUE_ITEM
       */
      private String encodedValue;
  
      /**
       * @see JmxConstants#TYPE_ITEM
       */
      private String encodedType;
  
      private PropertyData() {
          super();
      }
  
  
      @SuppressWarnings("unchecked")
      private PropertyData(String key, T value, String preservedBaseType) throws IllegalArgumentException {
          if (key == null) {
              throw new IllegalArgumentException("Argument key cannot be null");
          }
          if (value == null) {
              throw new IllegalArgumentException("Argument value cannot be null");
          }
          this.key = key;
          this.value = value;
          Class<T> type = (Class<T>) value.getClass();
          if (type.isArray()) {
              this.encodedType = ARRAY_OF + type.getComponentType().getSimpleName();
              StringBuilder builder = new StringBuilder();
              int length = Array.getLength(value);
              boolean useDelimiter = false;
              for (int i = 0; i < length; i++) {
                  if (useDelimiter) {
                      builder.append(",");
                  } else {
                      useDelimiter = true;
                  }
                  builder.append(Array.get(value, i));
              }
              this.encodedValue = builder.toString();
          } else if (type.equals(Vector.class)) {
              Vector<?> vector = (Vector<?>) value;
              Class<? extends Object> componentType = Object.class;
              if (vector.size() > 0) {
                  componentType = vector.firstElement().getClass();
              }
              this.encodedType = VECTOR_OF + componentType.getSimpleName();
              StringBuilder builder = new StringBuilder();
              Vector<?> valueVector = (Vector<?>) value;
              boolean useDelimiter = false;
              for (Object val : valueVector) {
                  if (useDelimiter) {
                      builder.append(",");
                  } else {
                      useDelimiter = true;
                  }
                  builder.append(val);
              }
              this.encodedValue = builder.toString();
          } else if (List.class.isAssignableFrom(type)) {
              // Lists are encoded as Arrays...
              List<?> list = (List<?>) value;
              Class<?> componentType = Object.class;
              if (list.size() > 0)
                  componentType = list.get(0).getClass();
  
              this.encodedType = ARRAY_OF + componentType.getSimpleName();
              StringBuilder builder = new StringBuilder();
              boolean useDelimiter = false;
              for (Object o : list) {
                  if (useDelimiter) {
                      builder.append(",");
                  } else {
                      useDelimiter = true;
                  }
                  builder.append(o);
              }
              this.encodedValue = builder.toString();
          } else {
              this.encodedType = (preservedBaseType == null) ? type.getSimpleName() : preservedBaseType;
              this.encodedValue = value.toString();
          }
      }
  
      /**
       * Static factory method for <code>PropertyData</code> instance parameterized by value's type
       * @param <T>
       * @param key
       * @param value an instance of {@link JmxConstants#SCALAR}
       * @return
       * @throws IllegalArgumentException if key or value are null or value's type cannot be encoded
       */
      public static <T> PropertyData<T> newInstance(String key, T value) throws IllegalArgumentException {
          return new PropertyData<T>(key, value, null);
      }
  
      /**
       * Static factory method for <code>PropertyData</code> instance which preserves encoded type
       * information for primitive int type
       * @param key
       * @param value
       * @return
       * @throws IllegalArgumentException if key or value are null or value's type cannot be encoded
       */
      public static PropertyData<Integer> newInstance(String key, int value) throws IllegalArgumentException {
          return new PropertyData<Integer>(key, value, P_INT);
      }
  
      /**
       * Static factory method for <code>PropertyData</code> instance which preserves encoded type
       * information for primitive long type
       * @param key
       * @param value
       * @return
       * @throws IllegalArgumentException if key or value are null or value's type cannot be encoded
       */
      public static PropertyData<Long> newInstance(String key, long value) throws IllegalArgumentException {
          return new PropertyData<Long>(key, value, P_LONG);
      }
  
      /**
       * Static factory method for <code>PropertyData</code> instance which preserves encoded type
       * information for primitive float type
       * @param key
       * @param value
       * @return
       * @throws IllegalArgumentException if key or value are null or value's type cannot be encoded
       */
      public static PropertyData<Float> newInstance(String key, float value) throws IllegalArgumentException {
          return new PropertyData<Float>(key, value, P_FLOAT);
      }
  
      /**
       * Static factory method for <code>PropertyData</code> instance which preserves encoded type
       * information for primitive double type
       * @param key
       * @param value
       * @return
       * @throws IllegalArgumentException if key or value are null or value's type cannot be encoded
       */
      public static PropertyData<Double> newInstance(String key, double value) throws IllegalArgumentException {
          return new PropertyData<Double>(key, value, P_DOUBLE);
      }
  
      /**
       * Static factory method for <code>PropertyData</code> instance which preserves encoded type
       * information for primitive byte type
       * @param key
       * @param value
       * @return
       * @throws IllegalArgumentException if key or value are null or value's type cannot be encoded
       */
      public static PropertyData<Byte> newInstance(String key, byte value) throws IllegalArgumentException {
          return new PropertyData<Byte>(key, value, P_BYTE);
      }
  
      /**
       * Static factory method for <code>PropertyData</code> instance which preserves encoded type
       * information for primitive char type
       * @param key
       * @param value
       * @return
       * @throws IllegalArgumentException if key or value are null or value's type cannot be encoded
       */
      public static PropertyData<Character> newInstance(String key, char value) throws IllegalArgumentException {
          return new PropertyData<Character>(key, value, P_CHAR);
      }
  
      /**
       * Static factory method for <code>PropertyData</code> instance which preserves encoded type
       * information for primitive boolean type
       * @param key
       * @param value
       * @return
       * @throws IllegalArgumentException if key or value are null or value's type cannot be encoded
       */
      public static PropertyData<Boolean> newInstance(String key, boolean value) throws IllegalArgumentException {
          return new PropertyData<Boolean>(key, value, P_BOOLEAN);
      }
  
  
      /**
       * Returns CompositeData representing a Property typed by {@link JmxConstants#PROPERTY_TYPE}.
       * @return
       */
      public CompositeData toCompositeData() {
          CompositeData result = null;
          Map<String, Object> items = new HashMap<String, Object>();
          items.put(KEY, this.key);
          items.put(VALUE, this.encodedValue);
          items.put(TYPE, this.encodedType);
          try {
              result = new CompositeDataSupport(PROPERTY_TYPE, items);
          } catch (OpenDataException e) {
              throw new IllegalStateException("Failed to create CompositeData for Property [" + this.key + ":" + this.value + "]", e);
          }
          return result;
      }
  
      /**
       * Constructs a <code>PropertyData</code> object from the given <code>CompositeData</code>
       * @param compositeData
       * @return
       * @throws IlleglArgumentException if compositeData is null or not of type {@link JmxConstants#PROPERTY_TYPE}
       */
      @SuppressWarnings("unchecked")
      public static <T> PropertyData<T> from(CompositeData compositeData) throws IllegalArgumentException {
          if ( compositeData == null ) {
              throw new IllegalArgumentException("Argument compositeData cannot be null");
          }
          if (!compositeData.getCompositeType().equals(PROPERTY_TYPE)) {
              throw new IllegalArgumentException("Invalid CompositeType [" + compositeData.getCompositeType() + "]");
          }
          PropertyData propertyData = new PropertyData();
          propertyData.key = (String) compositeData.get(KEY);
          propertyData.encodedType = (String) compositeData.get(TYPE);
          propertyData.encodedValue = (String) compositeData.get(VALUE);
          if (propertyData.encodedType == null || propertyData.encodedType.length() < 1) {
              throw new IllegalArgumentException ("Cannot determine type from compositeData : " + compositeData);
          }
          StringTokenizer values = new StringTokenizer(propertyData.encodedValue, ",");
          int valuesLength = values.countTokens();
          if (propertyData.encodedType.startsWith(ARRAY_OF)) {
              String[] arrayTypeParts = propertyData.encodedType.split("\\s");
              if (arrayTypeParts.length < 3) {
                  throw new IllegalArgumentException("Cannot parse Array type from type item : " + propertyData.encodedType);
              }
              String arrayTypeName = arrayTypeParts[2].trim();
              if (!types.containsKey(arrayTypeName)) {
                  throw new IllegalArgumentException ("Cannot determine type from value : " + arrayTypeName);
              }
              Class<? extends Object> arrayType = types.get(arrayTypeName);
              propertyData.value = Array.newInstance(arrayType, valuesLength);
              int index = 0;
              while (values.hasMoreTokens()) {
                  Array.set(propertyData.value, index++, fromString(arrayType, values.nextToken()));
              }
          } else if (propertyData.encodedType.startsWith(VECTOR_OF)) {
              String[] vectorTypeParts = propertyData.encodedType.split("\\s");
              if (vectorTypeParts.length < 3) {
                  throw new IllegalArgumentException("Cannot parse Array type from type item : " + propertyData.encodedType);
              }
              String vectorTypeName = vectorTypeParts[2].trim();
              if (!types.containsKey(vectorTypeName)) {
                  throw new IllegalArgumentException ("Cannot determine type from value : " + vectorTypeName);
              }
              Class<? extends Object> vectorType = types.get(vectorTypeName);
              Vector vector = new Vector();
              while (values.hasMoreTokens()) {
                  vector.add(fromString(vectorType, values.nextToken()));
              }
              propertyData.value = vector;
          } else {
              if (!types.containsKey(propertyData.encodedType)) {
                  throw new IllegalArgumentException ("Cannot determine type from value : " + propertyData.encodedType);
              }
              Class<? extends Object> valueType = types.get(propertyData.encodedType);
              propertyData.value = fromString(valueType, propertyData.encodedValue);
          }
          return propertyData;
      }
  
  
      public String getKey() {
          return key;
      }
  
      public T getValue() {
          return value;
      }
  
      public String getEncodedType() {
          return encodedType;
      }
  
      public String getEncodedValue() {
          return encodedValue;
      }
  
      public boolean isEncodingPrimitive() {
          return primitiveTypes.containsKey(encodedType);
      }
  
  }
  