blob: 0ebc88047d5f831f8280f0a8bc8fd22e92efe562 [file] [log] [blame]
/*
* 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.felix.ipojo.util;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.felix.ipojo.ComponentInstance;
import org.apache.felix.ipojo.ConfigurationException;
import org.apache.felix.ipojo.ConstructorInjector;
import org.apache.felix.ipojo.FieldInterceptor;
import org.apache.felix.ipojo.Handler;
import org.apache.felix.ipojo.InstanceManager;
import org.apache.felix.ipojo.parser.ParseUtils;
import org.osgi.framework.BundleContext;
/**
* Property class managing a managed value.
* This class managed the method invocation, field injection
* and constructor injection.
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class Property implements FieldInterceptor, ConstructorInjector {
/**
* Object used for an unvalued property.
*/
public static final Object NO_VALUE = new Object();
/**
* String value returned for property without values.
*/
public static final String UNVALUED = "UNVALUED";
/**
* The name of the property (field name if not set).
* Cannot change once set.
*/
private final String m_name;
/**
* The field of the property.
* Cannot change once set.
*/
private final String m_field;
/**
* The setter method of the property.
* Cannot change once set.
*/
private final Callback m_method;
/**
* The index of the parameter in case of
* constructor injection.
*/
private int m_index = -1;
/**
* The value of the property.
*/
private Object m_value = NO_VALUE;
/**
* The default value of the property.
*/
private Object m_defaultValue = NO_VALUE;
/**
* Flag tracking is the method was
* already called for the current value.
*/
private boolean m_invoked;
/**
* The type of the property.
*/
private final Class m_type;
/**
* The handler object to get the logger.
*/
private final Handler m_handler;
/**
* The instance manager.
*/
private final InstanceManager m_manager;
/**
* Creates a property.
* At least the method or the field need
* to be specified.
* @param name the name of the property (optional)
* @param field the name of the field
* @param method the method name
* @param value the initial value of the property (optional)
* @param type the the type of the property
* @param manager the instance manager
* @param handler the handler object which manage this property.
* @throws ConfigurationException if the property value cannot be set.
*/
public Property(String name, String field, String method, String value, String type, InstanceManager manager, Handler handler) throws ConfigurationException {
m_handler = handler;
m_manager = manager;
m_field = field;
if (name == null) {
if (m_field == null) {
m_name = method;
} else {
m_name = field;
}
} else {
m_name = name;
}
m_type = computeType(type, manager.getGlobalContext());
if (value != null) {
m_value = create(m_type, value);
m_defaultValue = m_value;
}
if (method != null) {
m_method = new Callback(method, new String[] { m_type.getName() }, false, manager);
} else {
m_method = null;
}
}
/**
* Creates a property.
* At least the method or the field need
* to be specified.
* @param name the name of the property (optional)
* @param field the name of the field
* @param method the method name
* @param value the initial value of the property (optional)
* @param manager the instance manager
* @param handler the handler object which manage this property.
* @throws ConfigurationException if the property value cannot be set.
*/
public Property(String name, String field, String method, Object value, InstanceManager manager, Handler handler) throws ConfigurationException {
m_handler = handler;
m_manager = manager;
m_field = field;
if (value == null) {
throw new ConfigurationException("Cannot create properties without a value");
}
if (name == null) {
if (m_field == null) {
m_name = method;
} else {
m_name = field;
}
} else {
m_name = name;
}
m_type = value.getClass();
m_value = value;
m_defaultValue = m_value;
if (method != null) {
m_method = new Callback(method, new String[] { m_type.getName() }, false, manager);
} else {
m_method = null;
}
}
public Property(String name, String field, String method, int index,
String value, String type, InstanceManager manager, Handler handler) throws ConfigurationException {
this(name, field, method, value, type, manager, handler);
m_index = index;
}
/**
* Computes and returns the property type according to the given type name.
* @param type the the type name
* @param context the bundle context (used to load classes)
* @return the class of the given type
* @throws ConfigurationException if an error occurs when loading the type class for non-primitive types.
*/
public static Class computeType(String type, BundleContext context) throws ConfigurationException {
// Array :
if (type.endsWith("[]")) {
return computeArrayType(type, context);
} else {
// Syntactic sugar to avoid writing java.lang.String
if ("string".equals(type) || "String".equals(type)) {
return java.lang.String.class;
} else if ("boolean".equals(type)) {
return Boolean.TYPE;
} else if ("byte".equals(type)) {
return Byte.TYPE;
} else if ("short".equals(type)) {
return Short.TYPE;
} else if ("int".equals(type)) {
return Integer.TYPE;
} else if ("long".equals(type)) {
return Long.TYPE;
} else if ("float".equals(type)) {
return Float.TYPE;
} else if ("double".equals(type)) {
return Double.TYPE;
} else if ("char".equals(type)) {
return Character.TYPE;
} else {
// Non array, complex type.
try {
return context.getBundle().loadClass(type);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("Class not found exception in setValue on " + type, e);
} catch (SecurityException e) {
throw new ConfigurationException("Security exception in setValue on " + type, e);
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Argument issue when calling the constructor of the type " + type, e);
}
}
}
}
/**
* Gets the Class object of a type array.
* @param type the string descriptor of the type (must end by [] )
* @param context the bundle context (used to load classes)
* @return the Class object of the given type array.
* @throws ConfigurationException if the class cannot be loaded
*/
private static Class computeArrayType(String type, BundleContext context) throws ConfigurationException {
// Note: Harmony does't support the type[].class notation.
// An empty array has to be created to get the class object.
String internalType = type.substring(0, type.length() - 2);
if ("string".equals(internalType) || "String".equals(internalType)) {
return new String[0].getClass();
}
if ("boolean".equals(internalType)) {
return new boolean[0].getClass();
}
if ("byte".equals(internalType)) {
return new byte[0].getClass();
}
if ("short".equals(internalType)) {
return new short[0].getClass();
}
if ("int".equals(internalType)) {
return new int[0].getClass();
}
if ("long".equals(internalType)) {
return new long[0].getClass();
}
if ("float".equals(internalType)) {
return new float[0].getClass();
}
if ("double".equals(internalType)) {
return new double[0].getClass();
}
if ("char".equals(internalType)) {
return new char[0].getClass();
}
// Complex array type.
try {
Class clazz = context.getBundle().loadClass(internalType);
Object[] object = (Object[]) Array.newInstance(clazz, 0);
return object.getClass();
} catch (ClassNotFoundException e) {
throw new ConfigurationException("Class not found exception in setValue on " + internalType, e);
} catch (SecurityException e) {
throw new ConfigurationException("Security Exception in setValue on " + internalType, e);
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Argument issue when calling the constructor of the type " + internalType, e);
}
}
public String getName() {
return m_name;
}
public String getField() {
return m_field;
}
public String getType() {
return m_type.getName();
}
/**
* Gets the method name,
* <code>null</code> if no method.
* @return the method name.
*/
public String getMethod() {
if (m_method == null) { return null; }
return m_method.getMethod();
}
/**
* Checks if the property has a method callback.
* @return <code>true</code> if the property has a method.
*/
public boolean hasMethod() {
return m_method != null;
}
/**
* Gets the parameter index.
* @return the parameter index or <code>-1</code>
* if this property is not injected using constructor
* parameter.
*/
public int getParameterIndex() {
return m_index;
}
/**
* Checks if the property has a field.
* @return <code>true</code> if the property has a field.
*/
public boolean hasField() {
return m_field != null;
}
public synchronized Object getValue() {
return m_value;
}
/**
* Gets the initial value of the property.
* @return the default value.
*/
public Object getDefaultValue() {
return m_defaultValue;
}
/**
* Gets the NO VALUE Object.
* This method returns the object to inject when the property
* was not assigned to a value.
* @param type the type of the value.
* @return the object to inject when the property has no value.
*/
private static Object getNoValue(Class type) {
if (Boolean.TYPE.equals(type)) { return Boolean.FALSE; }
if (Byte.TYPE.equals(type)) { return new Byte((byte) 0); }
if (Short.TYPE.equals(type)) { return new Short((short) 0); }
if (Integer.TYPE.equals(type)) { return new Integer(0); }
if (Long.TYPE.equals(type)) { return new Long(0); }
if (Float.TYPE.equals(type)) { return new Float(0); }
if (Double.TYPE.equals(type)) { return new Double(0); }
if (Character.TYPE.equals(type)) { return new Character((char) 0); }
// If all other case, return null.
return null;
}
/**
* Sets the value of the property.
* @param value the new value.
*/
public void setValue(Object value) {
synchronized (this) {
// Is the object is directly assignable to the property, affect it.
if (isAssignable(m_type, value)) {
m_value = value;
} else {
// If the object is a String, we must recreate the object from the String form
if (value instanceof String) {
try {
m_value = create(m_type, (String) value);
} catch (ConfigurationException e) {
throw new ClassCastException("Incompatible type for the property " + m_name + " : " + e.getMessage());
}
} else {
// Error, the given property cannot be injected.
throw new ClassCastException("Incompatible type for the property " + m_name + " " + m_type.getName() + " expected, "
+ value.getClass() + " found");
}
}
m_invoked = false;
}
}
/**
* Checks if the given value is assignable to the given type.
* @param type the class of the type
* @param value the object to check
* @return <code>true</code> if the object is assignable in the property of type 'type'.
*/
public static boolean isAssignable(Class type, Object value) {
if (value == null || type.isInstance(value) || value == Property.NO_VALUE) { // When the value is null, the assign works necessary.
return true;
} else if (type.isPrimitive()) {
// Manage all boxing types.
if (value instanceof Boolean && Boolean.TYPE.equals(type)) { return true; }
if (value instanceof Byte && Byte.TYPE.equals(type)) { return true; }
if (value instanceof Short && Short.TYPE.equals(type)) { return true; }
if (value instanceof Integer && Integer.TYPE.equals(type)) { return true; }
if (value instanceof Long && Long.TYPE.equals(type)) { return true; }
if (value instanceof Float && Float.TYPE.equals(type)) { return true; }
if (value instanceof Double && Double.TYPE.equals(type)) { return true; }
if (value instanceof Character && Character.TYPE.equals(type)) { return true; }
return false;
} else {
// Else return false.
return false;
}
}
/**
* Creates an object of the given type with the given String value.
* @param type the type of the returned object
* @param strValue the String value.
* @return the object of type 'type' created from the String 'value'
* @throws ConfigurationException if the object cannot be created.
*/
public static Object create(Class type, String strValue) throws ConfigurationException {
if (Boolean.TYPE.equals(type)) {
return Boolean.valueOf(strValue);
}
if (Byte.TYPE.equals(type)) { return new Byte(strValue); }
if (Short.TYPE.equals(type)) { return new Short(strValue); }
if (Integer.TYPE.equals(type)) { return new Integer(strValue); }
if (Long.TYPE.equals(type)) { return new Long(strValue); }
if (Float.TYPE.equals(type)) { return new Float(strValue); }
if (Double.TYPE.equals(type)) { return new Double(strValue); }
// Character is a bit tricky, it's a boxing type, but there is not creator taking a String as parameter.
if (Character.TYPE.equals(type) || Character.class.equals(type)) { return strValue.charAt(0); }
// Array :
if (type.isArray()) {
return createArrayObject(type.getComponentType(), ParseUtils.parseArrays(strValue));
}
// Enum :
if (type.getSuperclass() != null && type.getSuperclass().getName().equals("java.lang.Enum")) {
try {
Method valueOf = type.getMethod("valueOf", new Class[] {String.class});
if (! valueOf.isAccessible()) {
valueOf.setAccessible(true);
}
// Invoke the static method
return valueOf.invoke(null, new String[] {strValue});
} catch (InvocationTargetException e) {
throw new ConfigurationException("Cannot create an enumerated value for " + type
+ " with " + strValue, e.getTargetException());
} catch (Exception e) {
throw new ConfigurationException("Cannot create an enumerated value for " + type
+ " with " + strValue, e);
}
}
// Else it is a neither a primitive type neither a String -> create
// the object by calling a constructor with a string in argument.
try {
Constructor cst = type.getConstructor(new Class[] { String.class });
return cst.newInstance(new Object[] { strValue });
} catch (SecurityException e) {
throw new ConfigurationException("Security exception during the creation of " + type, e);
} catch (NoSuchMethodException e) {
throw new ConfigurationException("Constructor not found exception during the creation of " + type, e);
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Argument issue when calling the constructor of the type " + type, e);
} catch (InstantiationException e) {
throw new ConfigurationException("Instantiation problem " + type, e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Illegal Access " + type, e);
} catch (InvocationTargetException e) {
throw new ConfigurationException("Invocation problem during the creation of " + type, e.getTargetException());
}
}
/**
* Creates an array object containing the type component type from
* the String array 'values'.
* @param interntype the internal type of the array.
* @param values the String array
* @return the array containing objects created from the 'values' array
* @throws ConfigurationException if the array cannot be created correctly
*/
public static Object createArrayObject(Class interntype, String[] values) throws ConfigurationException {
if (Boolean.TYPE.equals(interntype)) {
boolean[] bool = new boolean[values.length];
for (int i = 0; i < values.length; i++) {
bool[i] = Boolean.valueOf(values[i]).booleanValue();
}
return bool;
}
if (Byte.TYPE.equals(interntype)) {
byte[] byt = new byte[values.length];
for (int i = 0; i < values.length; i++) {
byt[i] = new Byte(values[i]).byteValue();
}
return byt;
}
if (Short.TYPE.equals(interntype)) {
short[] shor = new short[values.length];
for (int i = 0; i < values.length; i++) {
shor[i] = new Short(values[i]).shortValue();
}
return shor;
}
if (Integer.TYPE.equals(interntype)) {
int[] ints = new int[values.length];
for (int i = 0; i < values.length; i++) {
ints[i] = new Integer(values[i]).intValue();
}
return ints;
}
if (Long.TYPE.equals(interntype)) {
long[] longs = new long[values.length];
for (int i = 0; i < values.length; i++) {
longs[i] = new Long(values[i]).longValue();
}
return longs;
}
if (Float.TYPE.equals(interntype)) {
float[] floats = new float[values.length];
for (int i = 0; i < values.length; i++) {
floats[i] = new Float(values[i]).floatValue();
}
return floats;
}
if (Double.TYPE.equals(interntype)) {
double[] doubles = new double[values.length];
for (int i = 0; i < values.length; i++) {
doubles[i] = new Double(values[i]).doubleValue();
}
return doubles;
}
if (Character.TYPE.equals(interntype)) {
char[] chars = new char[values.length];
for (int i = 0; i < values.length; i++) {
chars[i] = values[i].toCharArray()[0];
}
return chars;
}
// Else it is a neither a primitive type -> create the
// object by calling a constructor with a string in argument.
try {
Constructor cst = interntype.getConstructor(new Class[] { String.class });
Object[] object = (Object[]) Array.newInstance(interntype, values.length);
for (int i = 0; i < values.length; i++) {
object[i] = cst.newInstance(new Object[] { values[i].trim() });
}
return object;
} catch (NoSuchMethodException e) {
throw new ConfigurationException("Constructor not found exception in setValue on " + interntype.getName(), e);
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Argument issue when calling the constructor of the type " + interntype.getName(), e);
} catch (InstantiationException e) {
throw new ConfigurationException("Instantiation problem " + interntype.getName(), e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Illegal Access Exception in " + interntype.getName(), e);
} catch (InvocationTargetException e) {
throw new ConfigurationException("Invocation problem " + interntype.getName(), e.getTargetException());
}
}
/**
* Clears the invoked flag.
* Then, despite the setter was already called,
* it will be invoked another times.
*/
public synchronized void reset() {
m_invoked = false;
}
/**
* Invokes the setter method on the given pojo object.
* If no specified pojo object, it calls on each created pojo object.
* @param instance the created object (could be <code>null</code>)
*/
public synchronized void invoke(Object instance) {
if (m_invoked) {
return; // Already called.
}
if (m_value == NO_VALUE) {
// Don't call method if no value
return;
}
try {
if (instance == null) {
m_method.call(new Object[] { m_value });
} else {
m_method.call(instance, new Object[] { m_value });
}
m_invoked = true;
} catch (NoSuchMethodException e) {
m_handler.error("The method " + m_method + " does not exist in the implementation class " + m_manager.getClassName(), e);
m_manager.stop();
} catch (IllegalAccessException e) {
m_handler.error("The method " + m_method + " is not accessible in the implementation class " + m_manager.getClassName(), e);
m_manager.stop();
} catch (InvocationTargetException e) {
m_handler.error("The method " + m_method + " in the implementation class " + m_manager.getClassName() + "throws an exception : " + e.getTargetException().getMessage(), e.getTargetException());
m_manager.setState(ComponentInstance.INVALID);
}
}
/**
* A field value is required by the object 'pojo'.
* @param pojo the POJO object
* @param fieldName the field
* @param value the last value
* @return the value if the handler want to inject this value.
* @see org.apache.felix.ipojo.FieldInterceptor#onGet(java.lang.Object, java.lang.String, java.lang.Object)
*/
public synchronized Object onGet(Object pojo, String fieldName, Object value) {
if (m_value == NO_VALUE) {
return getNoValue(m_type);
}
return m_value;
}
/**
* The field 'field' receives a new value.
* @param pojo the pojo
* @param fieldName the field name
* @param value the new value
* @see org.apache.felix.ipojo.FieldInterceptor#onSet(java.lang.Object, java.lang.String, java.lang.Object)
*/
public synchronized void onSet(Object pojo, String fieldName, Object value) {
if (m_value == null || ! m_value.equals(value)) {
setValue(value);
}
}
/**
* Gets the object to inject as constructor parameter.
* @param index the constructor parameter index
* @return the object to inject, so the property value.
* @see org.apache.felix.ipojo.ConstructorInjector#getConstructorParameter(int)
*/
public Object getConstructorParameter(int index) {
if (m_index != index) {
return null;
}
if (m_value == NO_VALUE) {
return getNoValue(m_type);
}
return m_value;
}
/**
* Gets the type of the constructor parameter to inject.
* @param index the parameter index
* @return the Class of the property.
* @see org.apache.felix.ipojo.ConstructorInjector#getConstructorParameterType(int)
*/
public Class getConstructorParameterType(int index) {
if (m_index != index) {
return null;
}
return m_type;
}
/**
* Gets the handler managing the property.
* @return the configuration handler.
*/
public Handler getHandler() {
return m_handler;
}
}