/************************************************************** * * 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 com.sun.star.lib.uno.helper; import com.sun.star.uno.Type; import com.sun.star.lang.EventObject; import com.sun.star.lang.WrappedTargetException; import com.sun.star.uno.TypeClass; import com.sun.star.uno.AnyConverter; import com.sun.star.uno.XInterface; import com.sun.star.uno.Any; import com.sun.star.uno.UnoRuntime; import com.sun.star.beans.XPropertyChangeListener; import com.sun.star.beans.XVetoableChangeListener; import com.sun.star.beans.PropertyChangeEvent; import com.sun.star.beans.XPropertySet; import com.sun.star.beans.Property; import com.sun.star.beans.PropertyAttribute; import com.sun.star.beans.UnknownPropertyException; import com.sun.star.beans.XPropertiesChangeListener; import com.sun.star.beans.XPropertySetInfo; import com.sun.star.beans.XFastPropertySet; import com.sun.star.beans.PropertyVetoException; import com.sun.star.beans.XMultiPropertySet; import java.util.Iterator; import java.util.Collection; import java.util.HashMap; import java.lang.reflect.Field; import com.sun.star.lang.DisposedException; /** This class is an implementation of the interfaces com.sun.star.beans.XPropertySet, * com.sun.star.beans.XFastPropertySet and com.sun.star.beans.XMultiPropertySet. This * class has to be inherited to be used. The values of properties are stored in member * variables of the inheriting class. By overriding the methods * {@link #convertPropertyValue convertPropertyValue}, * {@link #setPropertyValueNoBroadcast setPropertyValueNoBroadcast} and * {@link #getPropertyValue(Property)} one can determine how * property values are stored. * When using the supplied implementations of this class then the member variables which * hold property values have to be declared in the class which inherits last in the inheriting * chain and they have to be public
* Properties have to be registered by one of the registerProperty methods. They take among other * arguments an Object named id which has to be a String that represents the name of * the member variable. The registering has to occur in the constructor of the inheriting class. * It is no allowed to add or change properties later on.
* Example: *
* public class Foo extends PropertySet * { * protected int intProp; * * public Foo() * { * registerProperty("PropertyA", 0, new Type(int.class), (short)0, "intProp"); * } * } * **/ public class PropertySet extends ComponentBase implements XPropertySet, XFastPropertySet, XMultiPropertySet { private HashMap _nameToPropertyMap; private HashMap _handleToPropertyMap; private HashMap _propertyToIdMap; private Property[] arProperties; private int lastHandle= 1; protected XPropertySetInfo propertySetInfo; protected MultiTypeInterfaceContainer aBoundLC= new MultiTypeInterfaceContainer(); protected MultiTypeInterfaceContainer aVetoableLC= new MultiTypeInterfaceContainer(); public PropertySet() { super(); initMappings(); } /** Registers a property with this helper class and associates the argument id with it. * id is used to identify the storage of the property value. How property values are stored * and retrieved is determined by the methods {@link #convertPropertyValue convertPropertyValue}, * {@link #setPropertyValueNoBroadcast setPropertyValueNoBroadcast} and {@link #getPropertyValue(Property) getPropertyValue} * These methods expect id to be a java.lang.String which represents the name of a member variable * which holds the property value. * Only properties which are registered can be accessed. Registration has to occur during * initialization of the inheriting class (i.e. within the contructor). * @param prop The property to be registered. * @param id Identifies the properties storage. * @see #getPropertyId */ protected void registerProperty(Property prop, Object id) { putProperty(prop); assignPropertyId(prop, id); } /** Registers a property with this helper class and associates the argument id with it. * It does the same as {@link #registerProperty(Property, Object)}. The first four * arguments are used to construct a Property object. * Registration has to occur during * initialization of the inheriting class (i.e. within the contructor) * @param name The property's name (Property.Name). * @param handle The property's handle (Property.Handle). * @param type The property's type (Property.Type). * @param attributes The property's attributes (Property.Attributes). * @param id Identifies the property's storage. */ protected void registerProperty(String name, int handle, Type type, short attributes, Object id) { Property p= new Property(name, handle, type, attributes); registerProperty(p, id); } /** Registers a property with this class and associates the argument id with it. * It does the same as {@link #registerProperty(Property, Object)}. The first three * arguments are used to construct a Property object. The value for the Property.Handle * is generated and does not have to be specified here. Use this method for registering * a property if you do not care about the Property's handles. * Registration has to occur during * initialization of the inheriting class (i.e. within the contructor). * @param name The property's name (Property.Name). * @param type The property's type (Property.Type). * @param attributes The property's attributes (Property.Attributes). * @param id Identifies the property's storage. */ protected void registerProperty(String name, Type type, short attributes, Object id) { Property p= new Property(name, lastHandle++, type, attributes); registerProperty(p, id); } /** Registers a property with this class. This method expects that property values * are stored in member variables as is the case if the methods convertPropertyValue, * setPropertyValueNoBroadcast and getPropertyValue(Property) are not overridden. * It is presumed that the type of the member variable * corresponds Property.Type. For example, if the TypeClass of Property.Type is to be * a TypeClass.SHORT then the member must be a short or java.lang.Short. * The handle for the property is generated.
* This method converts values by help of the com.sun.star.uno.AnyConverter which only does a few widening * conversions on integer types and floating point types. For example, there is the property PropA with a Type equivalent * to int.class and the * value of the property is to be stored in a member variable of type int with name intProp. Then setPropertyValue is * called: *
* set.setPropertyValue( "PropA", new Byte( (byte)111)); ** At some point setPropertyValue will call convertPropertyValue and pass in the Byte object. Since we allow * that Byte values can be used with the property and know that the value is to be stored in intProp (type int) * we convert the Byte object into an Integer object which is then returned in the out-parameter newVal. This * conversion is actually performed by the AnyConverter. Later * the setPropertyValueNoBroadcast is called with that Integer object and the int value can be easily extracted * from the object and be assigned to the member intProp. *
* The method handles Any arguments the same as Object arguments. That is, the setVal argument can
* be a java.lang.Boolean or a com.sun.star.uno.Any containing a java.lang.Boolean. Likewise, a member
* containing a property value can be a com.sun.star.uno.Any or an java.lang.Object.
* Then, no conversion is necessary, since they can hold all possible values. However, if
* the member is an Object and setVal is an Any then the object contained in the any is assigned to
* the member. The extra type information which exists as Type object in the Any will get lost. If this is not
* intended then use an Any variable rather then an Object.
* If a member is an Object or Any and the argument setVal is an Object, other than String or array,
* then it is presumed to be an UNO object and queried for XInterface. If successful, the out-param newVal
* returns the XInterface.
* If a member is an UNO interface, then setVal is queried for this interface and the result is returned.
* If setVal is null then newVal will be null too after return.
*
* If a property value is stored using a primitive type the the out-parameters
* curVal and newVal contain the respective wrapper class (e.g.java.lang.Byte, etc.).
* curVal is used in calls to the XVetoableChangeListener and XPropertyChangeListener.
*
* @param property - in-param property for which the data is to be converted.
* @param newVal - out-param which contains the converted value on return.
* @param curVal - out-param the current value of the property. It is used in calls to the
* XVetoableChangeListener and XPropertyChangeListener.
* @param setVal - in-param. The value that is to be converted so that it matches Property and the internally used
* dataformat for that property.
* @return true - Conversion was successful. newVal contains a valid value for the property. false -
* conversion failed for some reason.
* @throws com.sun.star.lang.IllegalArgumentException The value provided is unfit for the property.
* @throws com.sun.star.lang.WrappedTargetException - An exception occured during the conversion, that is to be made known
* to the caller.
*/
protected boolean convertPropertyValue(Property property, Object[] newVal, Object[]curVal, Object setVal)
throws com.sun.star.lang.IllegalArgumentException, WrappedTargetException, UnknownPropertyException
{
boolean ret= true;
try
{
// get the member name
String sMember= (String) getPropertyId(property);
if (sMember != null)
{
// use reflection to obtain the field that holds the property value
// Class.getDeclaredFields does not return inherited fields. One could use Class.getFields to
// also get inherited fields, but only those which are public.
Field propField= getClass().getDeclaredField(sMember);
if (propField != null)
{
curVal[0]= propField.get(this);
Class memberClass= propField.getType();
// MAYBEVOID: if setVal == null or it is an Any and getObject returns null, then a void value is to be set
// This works only if there are no primitive types. For those we use the respective wrapper classes.
// In this implementation, a null reference means void value.
boolean bVoidValue= false;
boolean bAnyVal= setVal instanceof Any;
if (bAnyVal)
bVoidValue= ((Any) setVal).getObject() == null;
else
bVoidValue= setVal == null;
if (bVoidValue && memberClass.isPrimitive())
throw new com.sun.star.lang.IllegalArgumentException("The implementation does not support the MAYBEVOID attribute for this property");
Object convObj= null;
//The member that keeps the value of the Property is an Any. It can contain all possible
//types, therefore a conversion is not necessary.
if (memberClass.equals(Any.class))
{
if (bAnyVal)
//parameter setVal is also an Any and can be used without further processing
convObj= setVal;
else
{
// Parameter setVal is not an Any. We need to construct an Any that contains
// the argument setVal.
// If setVal is an interface implementation then, we cannot constuct the
// Any with setVal.getClass(), because the Any.Type._typeClass would be TypeClass.UNKNOWN.
// We try to get an XInterface of setVal and set an XInterface type.
if (setVal instanceof XInterface)
{
XInterface xint= UnoRuntime.queryInterface(XInterface.class, setVal);
if (xint != null)
convObj= new Any(new Type(XInterface.class), xint);
}
// The member is an any, and the past in argument was null reference (MAYBEVOID is set)
else if (setVal == null)
{
// if the any member is still null we create a void any
if (curVal[0] == null)
convObj= new Any(new Type(), null);
else
{
//otherwise we create an Any with the same type as a value of null;
convObj= new Any( ((Any)curVal[0]).getType(), null);
}
}
else
convObj= new Any(new Type(setVal.getClass()), setVal);
}
}
else
convObj= convert(memberClass, setVal);
newVal[0]= convObj;
}
}
else
throw new UnknownPropertyException("Property " + property.Name + " is unknown");
}
catch (java.lang.NoSuchFieldException e)
{
throw new WrappedTargetException("Field does not exist", this, e);
}
catch (java.lang.IllegalAccessException e)
{
throw new WrappedTargetException("", this ,e);
}
return ret;
}
private boolean checkType(Object obj)
{
if (obj == null
|| obj instanceof Boolean
|| obj instanceof Character
|| obj instanceof Number
|| obj instanceof String
|| obj instanceof XInterface
|| obj instanceof Type
|| obj instanceof com.sun.star.uno.Enum
|| obj.getClass().isArray())
return true;
return false;
}
// Param object can be an Any or other object. If obj is null then the return value is null
private Object convert( Class cl, Object obj) throws com.sun.star.lang.IllegalArgumentException
{
Object retVal= null;
//The member that keeps the value of the Property is an Object.Objects are similar to Anys in that they can
// hold all types.
if (obj == null || (obj instanceof Any && ((Any) obj).getObject() == null))
retVal= null;
else if(cl.equals(Object.class))
{
if (obj instanceof Any)
obj= ((Any) obj).getObject();
retVal= obj;
}
else if(cl.equals(boolean.class))
retVal= new Boolean(AnyConverter.toBoolean(obj));
else if (cl.equals(char.class))
retVal= new Character(AnyConverter.toChar(obj));
else if (cl.equals(byte.class))
retVal= new Byte(AnyConverter.toByte(obj));
else if (cl.equals(short.class))
retVal= new Short(AnyConverter.toShort(obj));
else if (cl.equals(int.class))
retVal= new Integer(AnyConverter.toInt(obj));
else if (cl.equals(long.class))
retVal= new Long(AnyConverter.toLong(obj));
else if (cl.equals(float.class))
retVal= new Float(AnyConverter.toFloat(obj));
else if (cl.equals(double.class))
retVal= new Double(AnyConverter.toDouble(obj));
else if (cl.equals(String.class))
retVal= AnyConverter.toString(obj);
else if (cl.isArray())
retVal= AnyConverter.toArray(obj);
else if (cl.equals(Type.class))
retVal= AnyConverter.toType(obj);
else if (cl.equals(Boolean.class))
retVal= new Boolean(AnyConverter.toBoolean(obj));
else if (cl.equals(Character.class))
retVal= new Character(AnyConverter.toChar(obj));
else if (cl.equals(Byte.class))
retVal= new Byte(AnyConverter.toByte(obj));
else if (cl.equals(Short.class))
retVal= new Short(AnyConverter.toShort(obj));
else if (cl.equals(Integer.class))
retVal= new Integer(AnyConverter.toInt(obj));
else if (cl.equals(Long.class))
retVal= new Long(AnyConverter.toLong(obj));
else if (cl.equals(Float.class))
retVal= new Float(AnyConverter.toFloat(obj));
else if (cl.equals(Double.class))
retVal= new Double(AnyConverter.toDouble(obj));
else if (XInterface.class.isAssignableFrom(cl))
retVal= AnyConverter.toObject(new Type(cl), obj);
else if (com.sun.star.uno.Enum.class.isAssignableFrom(cl))
retVal= AnyConverter.toObject(new Type(cl), obj);
else
throw new com.sun.star.lang.IllegalArgumentException("Could not convert the argument");
return retVal;
}
/** Sets the value of a property. In this implementation property values are stored in member variables
* (see {@link #convertPropertyValue convertPropertyValue} Notification of property listeners
* does not occur in this method. By overriding this method one can take full control about how property values
* are stored. But then, the {@link #convertPropertyValue convertPropertyValue} and
* {@link #getPropertyValue(Property)} must be overridden too.
*
* A Property with the MAYBEVOID attribute set, is stored as null value. Therefore the member variable must be
* an Object in order to make use of the property attribute. An exception is Any. The Any variable can be initially null, but
* once it is set the reference will not become null again. If the value is to be set to
* void then a new Any will be stored
* with a valid type but without a value (i.e. Any.getObject returns null).
* If a property has the READONLY attribute set, and one of the setter methods, such as setPropertyValue, has been
* called, then this method is not going to be called.
* @param property the property for which the new value is set
* @param newVal the new value for the property.
* @throws com.sun.star.lang.WrappedTargetException An exception, which has to be made known to the caller,
* occured during the setting of the value.
*/
protected void setPropertyValueNoBroadcast(Property property, Object newVal)
throws WrappedTargetException
{
try
{
// get the member name
String sMember= (String) getPropertyId(property);
if (sMember != null)
{
// use reflection to obtain the field that holds the property value
// Class.getDeclaredFields does not return inherited fields. One could use Class.getFields to
// also get inherited fields, but only those which are public.
Field propField= getClass().getDeclaredField(sMember);
if (propField != null)
propField.set(this, newVal);
}
}
catch(java.lang.Exception e)
{
throw new WrappedTargetException("PropertySet.setPropertyValueNoBroadcast", this, e);
}
}
/** Retrieves the value of a property. This implementation presumes that the values are stored in member variables
* of the furthest inheriting class (see {@link #convertPropertyValue convertPropertyValue}) and that the
* variables are public. The property must have
* been registered, for example by {@link #registerProperty(Property, Object)}. The identifyer Object argument
* must have been a java.lang.String which was the name of the member variable holding the property value.
* When properties are to be stored differently one has to override this method as well as
* {@link #convertPropertyValue} and {@link #setPropertyValueNoBroadcast}.
* If a value is stored in a variable of a primitive type then this method returns an instance of the respective
* wrapper class (e.g. java.lang.Boolean).
* @param property The property for which the value is to be retrieved.
* @return The value of the property.
*/
protected Object getPropertyValue(Property property)
{
Object ret= null;
try
{
// get the member name
String sMember= (String) getPropertyId(property);
if (sMember != null)
{
// use reflection to obtain the field that holds the property value
// Class.getDeclaredFields does not return inherited fields. One could use Class.getFields to
// also get inherited fields, but only those which are public.
Field propField= getClass().getDeclaredField(sMember);
if (propField != null)
ret= propField.get(this);
}
}
catch(java.lang.NoSuchFieldException e)
{
throw new java.lang.RuntimeException(e);
}
catch(java.lang.IllegalAccessException e)
{
throw new java.lang.RuntimeException(e);
}
return ret;
}
/**
* This method fires events to XPropertyChangeListener,XVetoableChangeListener and
* XPropertiesChangeListener event sinks.
* To distinguish what listeners are to be called the argument bVetoable is to be set to true if
* a XVetoableChangeListener is meant. For XPropertyChangeListener and XPropertiesChangeListener
* it is to be set to false.
*
* @param properties Properties wich will be or have been affected.
* @param newValues the new values of the properties.
* @param oldValues the old values of the properties.
* @param bVetoable true means fire to VetoableChangeListener, false means fire to
* XPropertyChangedListener and XMultiPropertyChangedListener.
*/
protected void fire(
Property[] properties,
Object[] newValues,
Object[] oldValues,
boolean bVetoable ) throws PropertyVetoException
{
// Only fire, if one or more properties changed
int nNumProps= properties.length;
if (nNumProps > 0)
{
PropertyChangeEvent[] arEvts= new PropertyChangeEvent[nNumProps];
int nAffectedProps= 0;
// Loop over all changed properties to fill the event struct
for (int i= 0; i < nNumProps; i++)
{
if ((bVetoable && (properties[i].Attributes & PropertyAttribute.CONSTRAINED) > 0)
|| (!bVetoable && (properties[i].Attributes & PropertyAttribute.BOUND) > 0))
{
arEvts[i]= new PropertyChangeEvent(this, properties[i].Name, false,
properties[i].Handle, oldValues[i], newValues[i]);
nAffectedProps++;
}
}
// fire the events for all changed properties
for (int i= 0; i < nAffectedProps; i++)
{
// get the listener container for the property name
InterfaceContainer lc= null;
if (bVetoable)
lc= aVetoableLC.getContainer(arEvts[i].PropertyName);
else
lc= aBoundLC.getContainer(arEvts[i].PropertyName);
if (lc != null)
{
Iterator it= lc.iterator();
while( it.hasNext())
{
Object listener= it.next();
if (bVetoable)
((XVetoableChangeListener) listener).vetoableChange(arEvts[i]);
else
((XPropertyChangeListener) listener).propertyChange(arEvts[i]);
}
}
// broadcast to all listeners with "" property name
if(bVetoable)
lc= listenerContainer.getContainer(XVetoableChangeListener.class);
else
lc= listenerContainer.getContainer(XPropertyChangeListener.class);
if(lc != null)
{
Iterator it= lc.iterator();
while(it.hasNext() )
{
Object listener= it.next();
if( bVetoable ) // fire change Events?
((XVetoableChangeListener) listener).vetoableChange(arEvts[i]);
else
((XPropertyChangeListener) listener).propertyChange(arEvts[i]);
}
}
}
// fire at XPropertiesChangeListeners
// if nAffectedProps == 0 then there are no BOUND properties
if (!bVetoable && nAffectedProps > 0)
{
PropertyChangeEvent[] arReduced= new PropertyChangeEvent[nAffectedProps];
System.arraycopy(arEvts, 0, arReduced, 0, nAffectedProps);
InterfaceContainer lc= listenerContainer.getContainer(XPropertiesChangeListener.class);
if (lc != null)
{
Iterator it= lc.iterator();
while (it.hasNext())
{
XPropertiesChangeListener listener = (XPropertiesChangeListener) it.next();
// fire the hole event sequence to the XPropertiesChangeListener's
listener.propertiesChange( arEvts );
}
}
}
}
}
// XFastPropertySet--------------------------------------------------------------------------------
public void setFastPropertyValue(int nHandle, Object aValue ) throws UnknownPropertyException,
PropertyVetoException, com.sun.star.lang.IllegalArgumentException, WrappedTargetException
{
Property prop= getPropertyByHandle(nHandle);
if (prop == null)
throw new UnknownPropertyException(" The property with handle : " + nHandle +" is unknown");
setPropertyValue(prop, aValue);
}
// XFastPropertySet --------------------------------------------------------------------------------
public Object getFastPropertyValue(int nHandle ) throws UnknownPropertyException,
WrappedTargetException
{
Property prop= getPropertyByHandle(nHandle);
if (prop == null)
throw new UnknownPropertyException("The property with handle : " + nHandle + " is unknown");
return getPropertyValue(prop);
}
// XMultiPropertySet -----------------------------------------------------------------------------------
public void addPropertiesChangeListener(String[] propNames, XPropertiesChangeListener listener)
{
listenerContainer.addInterface(XPropertiesChangeListener.class, listener);
}
// XMultiPropertySet -----------------------------------------------------------------------------------
public void firePropertiesChangeEvent(String[] propNames, XPropertiesChangeListener listener)
{
// Build the events.
PropertyChangeEvent[] arEvents= new PropertyChangeEvent[propNames.length];
int eventCount= 0;
// get a snapshot of the current property values
synchronized (this)
{
for (int i= 0; i < propNames.length; i++)
{
Property prop= getProperty(propNames[i]);
if (prop != null)
{
Object value= null;
try
{
value= getPropertyValue(prop);
}
catch(Exception e)
{
continue;
}
arEvents[eventCount]= new PropertyChangeEvent(this, prop.Name,
false, prop.Handle, value, value);
eventCount++;
}
}
}
// fire events from unsynchronized section so as to prevent deadlocks
if (eventCount > 0)
{
// Reallocate the array of the events if necessary
if (arEvents.length != eventCount)
{
PropertyChangeEvent[] arPropsTmp= new PropertyChangeEvent[eventCount];
System.arraycopy(arEvents, 0, arPropsTmp, 0, eventCount);
arEvents= arPropsTmp;
}
listener.propertiesChange(arEvents);
}
}
// XMultiPropertySet -----------------------------------------------------------------------------------
/** If a value for a property could not be retrieved then the respective element in the returned
* array has the value null.
*/
public Object[] getPropertyValues(String[] propNames)
{
Object[] arValues= new Object[propNames.length];
synchronized (this)
{
for (int i= 0; i < propNames.length; i++)
{
Object value= null;
try
{
value= getPropertyValue(propNames[i]);
}
catch (Exception e)
{
}
arValues[i]= value;
}
}
return arValues;
}
// XMultiPropertySet -----------------------------------------------------------------------------------
public void removePropertiesChangeListener(XPropertiesChangeListener xPropertiesChangeListener)
{
listenerContainer.removeInterface(XPropertiesChangeListener.class, xPropertiesChangeListener);
}
// XMultiPropertySet -----------------------------------------------------------------------------------
/** If the array of property names containes an unknown property then it will be ignored.
*/
public void setPropertyValues(String[] propNames, Object[] values) throws PropertyVetoException, com.sun.star.lang.IllegalArgumentException, com.sun.star.lang.WrappedTargetException
{
for (int i= 0; i < propNames.length; i++)
{
try
{
setPropertyValue(propNames[i], values[i]);
}
catch (UnknownPropertyException e)
{
continue;
}
}
}
private class PropertySetInfo implements XPropertySetInfo
{
public com.sun.star.beans.Property[] getProperties()
{
return PropertySet.this.getProperties();
}
public com.sun.star.beans.Property getPropertyByName(String name) throws UnknownPropertyException
{
return getProperty(name);
}
public boolean hasPropertyByName(String name)
{
return getProperty(name) != null;
}
}
}