FELIX-4357: Support types beside String/String[] in @Property annotation.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1554080 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/api/Property.java b/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/api/Property.java
index 5c589b5..f760ee9 100644
--- a/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/api/Property.java
+++ b/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/api/Property.java
@@ -24,13 +24,45 @@
import java.lang.annotation.Target;
/**
- * Annotation used to describe a property key-value pair. It is used when
- * declaring {@link Component#properties()} attribute.
- * This annotation only support properties which type is either a
- * String, or a String array.
- * If you need to specify some properties with types other than Strings,
- * then use the {@link Start} annotation, which allows to annotate a <code>start</code>
- * method which may optionally return a Map of any service property types.
+ * Annotation used to describe a property key-value(s) pair. It is used for example when
+ * declaring {@link Component#properties()} attribute.<p>
+ *
+ * Property value(s) type is String by default, and the type is scalar if the value is single-valued,
+ * or an array if the value is multi-valued.
+ *
+ * Eight primitive types are supported:<p>
+ * <ul>
+ * <li> String (default type)
+ * <li> Long
+ * <li> Double
+ * <li> Float
+ * <li> Integer
+ * <li> Byte
+ * <li> Boolean
+ * <li> Short
+ * </ul>
+ *
+ * You can specify the type of a property either using a combination of <code>value</code> and <code>type</code> attributes,
+ * or using one of the <code>longValue/doubleValue/floatValue/intValue/byteValue/charValue/booleanValue/shortValue</code> attributes.
+ *
+ * Notice that you can also specify service properties dynamically by returning a Map from a method
+ * annotated with {@link Start}.
+ *
+ * <p>
+ * <h3>Usage Examples</h3>
+ * <blockquote>
+ *
+ * <pre>
+ * @Component(properties={
+ * @Property(name="p1", value="v")}) // String value type (scalar)
+ * @Property(name="p2", value={"s1", "s2")}) // Array of Strings
+ * @Property(name="service.ranking", intValue=10) // Integer value type (scalar)
+ * @Property(name="p3", intValue={1,2}) // Array of Integers
+ * @Property(name="p3", value={"1"), type=Long.class}) // Long value (scalar)
+ * class ServiceImpl implements Service {
+ * }
+ * </pre>
+ * </blockquote>
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
@@ -45,14 +77,70 @@
String name();
/**
- * Returns the property value
- * @return this property value
+ * Returns the property value(s). The property value(s) is (are)
+ * parsed using the <code>valueOf</code> method of the class specified in the #type attribute
+ * (which is <code>String</code> by default). When the property value is single-value, then
+ * the value type is scalar (not an array). If the property value is multi-valued, then the value type
+ * is an array of the type specified in the {@link #type()} attribute (String by default).
+ *
+ * @return this property value(s).
*/
- String value() default "";
+ String[] value() default {};
/**
- * Returns the property value as a String array.
- * @return this property value as a String array
+ * Specifies how the {@link #value()} or {@link #values()} attributes are parsed.
+ * @return the property value type (String by default) used to parse {@link #value()} or {@link #values()}
+ * attribtues
*/
- String[] values() default {};
+ Class<?> type() default String.class;
+
+ /**
+ * A Long value or an array of Long values.
+ */
+ long[] longValue() default {};
+
+ /**
+ * A Double value or an array of Double values.
+ */
+ double[] doubleValue() default {};
+
+ /**
+ * A Float value or an array of Float values.
+ */
+ float[] floatValue() default {};
+
+ /**
+ * An Integer value or an array of Integer values.
+ */
+ int[] intValue() default {};
+
+ /**
+ * A Byte value or an array of Byte values.
+ */
+ byte[] byteValue() default {};
+
+ /**
+ * A Character value or an array of Character values.
+ */
+ char[] charValue() default {};
+
+ /**
+ * A Boolean value or an array of Boolean values.
+ */
+ boolean[] booleanValue() default {};
+
+ /**
+ * A Short value or an array of Short values.
+ */
+ short[] shortValue() default {};
+
+ /**
+ * Returns an array of property values.
+ * The property value are parsed using the <code>valueOf</code> method of the class specified in the #type attribute
+ * (which is <code>String</code> by default).
+ *
+ * @return an array of property values.
+ * @deprecated use {@link #value()} attribute.
+ */
+ String[] values() default {};
}
diff --git a/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java b/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java
index 6a237c1..a17f3ec 100644
--- a/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java
+++ b/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java
@@ -21,10 +21,8 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import org.apache.felix.dm.annotation.api.AdapterService;
@@ -39,20 +37,23 @@
import org.apache.felix.dm.annotation.api.Init;
import org.apache.felix.dm.annotation.api.Inject;
import org.apache.felix.dm.annotation.api.LifecycleController;
+import org.apache.felix.dm.annotation.api.Registered;
import org.apache.felix.dm.annotation.api.ResourceAdapterService;
import org.apache.felix.dm.annotation.api.ResourceDependency;
import org.apache.felix.dm.annotation.api.ServiceDependency;
import org.apache.felix.dm.annotation.api.Start;
-import org.apache.felix.dm.annotation.api.Registered;
import org.apache.felix.dm.annotation.api.Stop;
import org.apache.felix.dm.annotation.api.Unregistered;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
import org.osgi.framework.Bundle;
import aQute.bnd.osgi.Annotation;
import aQute.bnd.osgi.ClassDataCollector;
import aQute.bnd.osgi.Clazz;
-import aQute.bnd.osgi.Verifier;
import aQute.bnd.osgi.Descriptors.TypeRef;
+import aQute.bnd.osgi.Verifier;
/**
* This is the scanner which does all the annotation parsing on a given class.
@@ -932,45 +933,255 @@
/**
* Parses a Property annotation (which represents a list of key-value pair).
+ * The properties are encoded using the following json form:
+ *
+ * properties ::= key-value-pair*
+ * key-value-pair ::= key value
+ * value ::= String | String[] | value-type
+ * value-type ::= jsonObject with value-type-info
+ * value-type-info ::= "type"=primitive java type
+ * "value"=String|String[]
+ *
+ * Exemple:
+ *
+ * "properties" : {
+ * "string-param" : "string-value",
+ * "string-array-param" : ["str1", "str2],
+ * "long-param" : {"type":"java.lang.Long", "value":"1"}}
+ * "long-array-param" : {"type":"java.lang.Long", "value":["1"]}}
+ * }
+ * }
+ *
* @param annotation the annotation where the Param annotation is defined
* @param attribute the attribute name which is of Param type
* @param writer the object where the parsed attributes are written
*/
private void parseProperties(Annotation annotation, EntryParam attribute, EntryWriter writer)
{
- Object[] parameters = annotation.get(attribute.toString());
- Map<String, Object> properties = new HashMap<String, Object>();
- if (parameters != null)
+ try
{
- for (Object p: parameters)
+ Object[] parameters = annotation.get(attribute.toString());
+ if (parameters != null)
{
- Annotation a = (Annotation) p;
- String name = (String) a.get("name");
- String value = (String) a.get("value");
- if (value != null)
+ JSONObject properties = new JSONObject();
+ for (Object p : parameters)
{
- properties.put(name, value);
- }
- else
- {
- Object[] values = a.get("values");
- if (values != null)
+ Annotation a = (Annotation) p;
+ String name = (String) a.get("name");
+
+ String type = a.get("type");
+ Class<?> classType;
+ try
{
- // the values is an Object array of actual strings, and we must convert it into a String array.
- properties.put(name, Arrays.asList(values).toArray(new String[values.length]));
+ classType = (type == null) ? String.class : Class.forName(Patterns.parseClass(type,
+ Patterns.CLASS, 1));
+ }
+ catch (ClassNotFoundException e)
+ {
+ // Theorically impossible
+ throw new IllegalArgumentException("Invalid Property type " + type
+ + " from annotation " + annotation + " in class " + m_className);
+ }
+
+ Object[] values;
+
+ if ((values = a.get("value")) != null)
+ {
+ values = checkPropertyType(name, classType, values);
+ addProperty(properties, name, values, classType);
+ }
+ else if ((values = a.get("values")) != null)
+ { // deprecated
+ values = checkPropertyType(name, classType, values);
+ addProperty(properties, name, values, classType);
+ }
+ else if ((values = a.get("longValue")) != null)
+ {
+ addProperty(properties, name, values, Long.class);
+ }
+ else if ((values = a.get("doubleValue")) != null)
+ {
+ addProperty(properties, name, values, Double.class);
+ }
+ else if ((values = a.get("floatValue")) != null)
+ {
+ addProperty(properties, name, values, Float.class);
+ }
+ else if ((values = a.get("intValue")) != null)
+ {
+ addProperty(properties, name, values, Integer.class);
+ }
+ else if ((values = a.get("byteValue")) != null)
+ {
+ addProperty(properties, name, values, Byte.class);
+ }
+ else if ((values = a.get("charValue")) != null)
+ {
+ addProperty(properties, name, values, Character.class);
+ }
+ else if ((values = a.get("booleanValue")) != null)
+ {
+ addProperty(properties, name, values, Boolean.class);
+ }
+ else if ((values = a.get("shortValue")) != null)
+ {
+ addProperty(properties, name, values, Short.class);
}
else
{
- throw new IllegalArgumentException("Invalid Property attribyte \"" + attribute
- + " from annotation " + annotation + " in class " + m_className);
+ throw new IllegalArgumentException("Missing Property value from annotation "
+ + annotation + " in class " + m_className);
}
}
+ writer.putJsonObject(attribute, properties);
}
- writer.putProperties(attribute, properties);
+ }
+ catch (JSONException e)
+ {
+ throw new IllegalArgumentException("UNexpected exception while parsing Property from annotation "
+ + annotation + " in class " + m_className, e);
}
}
/**
+ * Checks if a property contains values that are compatible with a give primitive type.
+ *
+ * @param name the property name
+ * @param values the values for the property name
+ * @param type the primitive type.
+ * @return the same property values, possibly modified if the type is 'Character' (the strings are converted to their character integer values).
+ */
+ private Object[] checkPropertyType(String name, Class<?> type, Object ... values)
+ {
+ if (type.equals(String.class))
+ {
+ return values;
+ } else if (type.equals(Long.class)) {
+ for (Object value : values) {
+ try {
+ Long.valueOf(value.toString());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_className
+ + " does not contain a valid Long value (" + value.toString() + ")");
+ }
+ }
+ } else if (type.equals(Double.class)) {
+ for (Object value : values) {
+ try {
+ Double.valueOf(value.toString());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_className
+ + " does not contain a valid Double value (" + value.toString() + ")");
+ }
+ }
+ } else if (type.equals(Float.class)) {
+ for (Object value : values) {
+ try {
+ Float.valueOf(value.toString());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_className
+ + " does not contain a valid Float value (" + value.toString() + ")");
+ }
+ }
+ } else if (type.equals(Integer.class)) {
+ for (Object value : values) {
+ try {
+ Integer.valueOf(value.toString());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_className
+ + " does not contain a valid Integer value (" + value.toString() + ")");
+ }
+ }
+ } else if (type.equals(Byte.class)) {
+ for (Object value : values) {
+ try {
+ Byte.valueOf(value.toString());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_className
+ + " does not contain a valid Byte value (" + value.toString() + ")");
+ }
+ }
+ } else if (type.equals(Character.class)) {
+ for (int i = 0; i < values.length; i++)
+ {
+ try
+ {
+ // If the string is already an integer, don't modify it
+ Integer.valueOf(values[i].toString());
+ }
+ catch (NumberFormatException e)
+ {
+ // Converter the character string to its corresponding integer code.
+ if (values[i].toString().length() != 1)
+ {
+ throw new IllegalArgumentException("Property \"" + name + "\" in class "
+ + m_className + " does not contain a valid Character value (" + values[i] + ")");
+ }
+ try
+ {
+ values[i] = Integer.valueOf(values[i].toString().charAt(0));
+ }
+ catch (NumberFormatException e2)
+ {
+ throw new IllegalArgumentException("Property \"" + name + "\" in class "
+ + m_className + " does not contain a valid Character value (" + values[i].toString()
+ + ")");
+ }
+ }
+ }
+ } else if (type.equals(Boolean.class)) {
+ for (Object value : values) {
+ if (! value.toString().equalsIgnoreCase("false") && ! value.toString().equalsIgnoreCase("true")) {
+ throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_className
+ + " does not contain a valid Boolean value (" + value.toString() + ")");
+ }
+ }
+ } else if (type.equals(Short.class)) {
+ for (Object value : values) {
+ try {
+ Short.valueOf(value.toString());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_className
+ + " does not contain a valid Short value (" + value.toString() + ")");
+ }
+ }
+ }
+
+ return values;
+ }
+
+ /**
+ * Adds a key/value(s) pair in a properties map
+ * @param properties the target properties map
+ * @param name the property name
+ * @param value the property value(s)
+ * @param type the property type
+ * @throws JSONException
+ */
+ private void addProperty(JSONObject props, String name, Object value, Class type) throws JSONException {
+ if (value.getClass().isArray())
+ {
+ Object[] array = (Object[]) value;
+ if (array.length == 1)
+ {
+ value = array[0];
+ }
+ }
+
+ if (type.equals(String.class))
+ {
+ props.put(name, value.getClass().isArray() ? new JSONArray(value) : value);
+ }
+ else
+ {
+ JSONObject jsonValue = new JSONObject();
+ jsonValue.put("type", type.getName());
+ jsonValue.put("value", value.getClass().isArray() ? new JSONArray(value) : value);
+ props.put(name, jsonValue);
+ }
+ }
+
+ /**
* Parse Inject annotation, used to inject some special classes in some fields
* (BundleContext/DependencyManager etc ...)
* @param annotation the Inject annotation
diff --git a/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/EntryWriter.java b/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/EntryWriter.java
index b59f920..8731dd8 100644
--- a/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/EntryWriter.java
+++ b/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/EntryWriter.java
@@ -113,39 +113,12 @@
}
/**
- * Put a Map parameter in the descriptor entry. The map values must be either Strings or Strings arrays.
+ * Puts a json object.
+ * @throws JSONException
*/
- public void putProperties(EntryParam param, Map<String, Object> properties)
+ public void putJsonObject(EntryParam param, JSONObject jsonObject) throws JSONException
{
- checkType(param.toString());
-
- try
- {
- JSONObject props = new JSONObject();
- for (String key: properties.keySet())
- {
- Object value = properties.get(key);
- if (value instanceof String)
- {
- props.put(key, value);
- }
- else if (value instanceof String[])
- {
- props.put(key, new JSONArray(Arrays.asList((String[]) value)));
- }
- else
- {
- throw new IllegalArgumentException("invalid property value: " + value);
- }
- }
- m_json.put(param.toString(), props);
- }
-
- catch (JSONException e)
- {
- throw new IllegalArgumentException("invalid properties for " + param + ": "
- + properties, e);
- }
+ m_json.put(param.toString(), jsonObject);
}
/**