/* * 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; } }