1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 package com.sun.star.lib.uno.helper;
24 
25 import com.sun.star.uno.Type;
26 import com.sun.star.lang.EventObject;
27 import com.sun.star.lang.WrappedTargetException;
28 import com.sun.star.uno.TypeClass;
29 import com.sun.star.uno.AnyConverter;
30 import com.sun.star.uno.XInterface;
31 import com.sun.star.uno.Any;
32 import com.sun.star.uno.UnoRuntime;
33 import com.sun.star.beans.XPropertyChangeListener;
34 import com.sun.star.beans.XVetoableChangeListener;
35 import com.sun.star.beans.PropertyChangeEvent;
36 import com.sun.star.beans.XPropertySet;
37 import com.sun.star.beans.Property;
38 import com.sun.star.beans.PropertyAttribute;
39 import com.sun.star.beans.UnknownPropertyException;
40 import com.sun.star.beans.XPropertiesChangeListener;
41 import com.sun.star.beans.XPropertySetInfo;
42 import com.sun.star.beans.XFastPropertySet;
43 import com.sun.star.beans.PropertyVetoException;
44 import com.sun.star.beans.XMultiPropertySet;
45 import java.util.Iterator;
46 import java.util.Collection;
47 import java.util.HashMap;
48 import java.lang.reflect.Field;
49 import com.sun.star.lang.DisposedException;
50 
51 
52 /** This class is an implementation of the interfaces com.sun.star.beans.XPropertySet,
53  *  com.sun.star.beans.XFastPropertySet and com.sun.star.beans.XMultiPropertySet. This
54  *  class has to be inherited to be used. The values of properties are stored in member
55  *  variables of the inheriting class. By overriding the methods
56  *  {@link #convertPropertyValue convertPropertyValue},
57  *  {@link #setPropertyValueNoBroadcast setPropertyValueNoBroadcast} and
58  *  {@link #getPropertyValue(Property)} one can determine how
59  *  property values are stored.
60  *  When using the supplied implementations of this class then the member variables which
61  *  hold property values have to be declared in the class which inherits last in the inheriting
62  *  chain and they have to be public<p>
63  *  Properties have to be registered by one of the registerProperty methods. They take among other
64  *  arguments an Object named <em>id</em> which has to be a String that represents the name of
65  *  the member variable. The registering has to occur in the constructor of the inheriting class.
66  *  It is no allowed to add or change properties later on.<p>
67  *  Example:
68  *  <pre>
69  *  public class Foo extends PropertySet
70  *  {
71  *      protected int intProp;
72  *
73  *      public Foo()
74  *      {
75  *          registerProperty("PropertyA", 0, new Type(int.class), (short)0, "intProp");
76  *      }
77  *  }
78  *
79  *  </pre>
80  */
81 public class PropertySet extends ComponentBase implements XPropertySet, XFastPropertySet,
82 XMultiPropertySet
83 {
84     private HashMap _nameToPropertyMap;
85     private HashMap _handleToPropertyMap;
86     private HashMap _propertyToIdMap;
87     private Property[] arProperties;
88 
89     private int lastHandle= 1;
90 
91     protected XPropertySetInfo propertySetInfo;
92     protected MultiTypeInterfaceContainer aBoundLC= new MultiTypeInterfaceContainer();
93     protected MultiTypeInterfaceContainer aVetoableLC= new MultiTypeInterfaceContainer();
PropertySet()94     public PropertySet()
95     {
96         super();
97         initMappings();
98     }
99 
100     /** Registers a property with this helper class and associates the argument <em>id</em> with it.
101      *  <em>id</em> is used to identify the storage of the property value. How property values are stored
102      *  and retrieved is determined by the methods {@link #convertPropertyValue convertPropertyValue},
103      *  {@link #setPropertyValueNoBroadcast setPropertyValueNoBroadcast} and {@link #getPropertyValue(Property) getPropertyValue}
104      *  These methods expect <em>id</em> to be a java.lang.String which represents the name of a member variable
105      *  which holds the property value.
106      *  Only properties which are registered can be accessed. Registration has to occur during
107      *  initialization of the inheriting class (i.e. within the contructor).
108      *  @param prop The property to be registered.
109      *  @param id Identifies the properties storage.
110      *  @see #getPropertyId
111      */
registerProperty(Property prop, Object id)112     protected void registerProperty(Property prop, Object id)
113     {
114         putProperty(prop);
115         assignPropertyId(prop, id);
116     }
117 
118     /** Registers a property with this helper class and associates the argument id with it.
119      *  It does the same as {@link #registerProperty(Property, Object)}. The first four
120      *  arguments are used to construct a Property object.
121      *  Registration has to occur during
122      *  initialization of the inheriting class (i.e. within the contructor)
123      *  @param name The property's name (Property.Name).
124      *  @param handle The property's handle (Property.Handle).
125      *  @param type The property's type (Property.Type).
126      *  @param attributes The property's attributes (Property.Attributes).
127      *  @param id Identifies the property's storage.
128      */
registerProperty(String name, int handle, Type type, short attributes, Object id)129     protected void registerProperty(String name, int handle, Type type, short attributes, Object id)
130     {
131         Property p= new Property(name, handle, type, attributes);
132         registerProperty(p, id);
133     }
134 
135     /** Registers a property with this  class and associates the argument id with it.
136      *  It does the same as {@link #registerProperty(Property, Object)}. The first three
137      *  arguments are used to construct a Property object. The value for the Property.Handle
138      *  is generated and does not have to be specified here. Use this method for registering
139      *  a property if you do not care about the Property's handles.
140      *  Registration has to occur during
141      *  initialization of the inheriting class (i.e. within the contructor).
142      *  @param name The property's name (Property.Name).
143      *  @param type The property's type (Property.Type).
144      *  @param attributes The property's attributes (Property.Attributes).
145      *  @param id Identifies the property's storage.
146      */
registerProperty(String name, Type type, short attributes, Object id)147     protected void registerProperty(String name, Type type, short attributes, Object id)
148     {
149         Property p= new Property(name, lastHandle++, type, attributes);
150         registerProperty(p, id);
151     }
152 
153     /** Registers a property with this class. This method expects that property values
154      *  are stored in member variables as is the case if the methods convertPropertyValue,
155      *  setPropertyValueNoBroadcast and getPropertyValue(Property) are not overridden.
156      *  It is presumed that the type of the member variable
157      *  corresponds Property.Type. For example, if the TypeClass of Property.Type is to be
158      *  a TypeClass.SHORT then the member must be a short or java.lang.Short.
159      *  The handle for the property is generated.<br>
160      *  If there is no member with the specified name or if the member has an incompatible type
161      *  then a com.sun.star.uno.RuntimeException is thrown.
162      *  @param propertyName The name of the property.
163      *  @param memberName The name of the member variable that holds the value of the property.
164      *  @param attributes The property attributes.
165      */
registerProperty(String propertyName, String memberName, short attributes)166     protected void registerProperty(String propertyName, String memberName, short attributes)
167     {
168         Field propField= null;
169         try
170         {
171             propField= getClass().getDeclaredField(memberName);
172         }
173         catch (NoSuchFieldException e)
174         {
175             throw new com.sun.star.uno.RuntimeException("there is no member variable: " + memberName);
176         }
177         Class cl= propField.getType();
178         Type t= new Type(cl);
179         if (t.getTypeClass() != TypeClass.UNKNOWN)
180         {
181             Property p= new Property(propertyName, lastHandle++,  t, attributes);
182             registerProperty(p,memberName);
183         }
184         else
185             throw new com.sun.star.uno.RuntimeException("the member has an unknown type: " + memberName);
186     }
187 
188     /** Registers a property with this class.
189      *  It is presumed that the name of property is equal to the name of the member variable
190      *  that holds the property value.
191      *  @param propertyName The name of the property and the member variable that holds the property's value.
192      *  @param attributes The property attributes.
193      *  @see #registerProperty(String, String, short)
194      */
registerProperty(String propertyName, short attributes)195     protected void registerProperty(String propertyName, short attributes)
196     {
197         registerProperty(propertyName, propertyName, attributes);
198     }
199 
200 
201 
202     /** Returns the Property object for a given property name or null if that property does
203      *  not exists (i.e. it has not been registered). Override this method
204      *  if you want to implement your own mapping from property names to Property objects.
205      *  Then you also have to override {@link #initMappings}, {@link #getProperties()} and
206      *  {@link #putProperty(Property)}.
207      *  @param propertyName The name of the property (Property.Name)
208      *  @return The Property object with the name <em>propertyName</em>.
209      */
getProperty(String propertyName)210     protected Property getProperty(String propertyName)
211     {
212         return (Property) _nameToPropertyMap.get(propertyName);
213     }
214 
215     /** Returns the Property object with a handle (Property.Handle) as specified by the argument
216      *  <em>nHandle</em>. The method returns null if there is no such property (i.e. it has not
217      *  been registered). Override this method if you want to implement your own mapping from handles
218      *  to Property objects. Then you also have to override {@link #initMappings}, {@link #putProperty(Property)}.
219      *  @param nHandle The handle of the property (Property.Handle).
220      *  @return The Property object with the handle <em>nHandle</em>
221      */
getPropertyByHandle(int nHandle)222     protected Property getPropertyByHandle(int nHandle)
223     {
224         return (Property) _handleToPropertyMap.get(new Integer(nHandle));
225     }
226 
227     /** Returns an array of all Property objects or an array of length null if there
228      *  are no properties. Override this method if you want to implement your own mapping from names
229      *  to Property objects. Then you also have to override {@link #initMappings}, {@link #getProperty(String)} and
230      *  {@link #putProperty}.
231      *  @return Array of all Property objects.
232      */
getProperties()233     protected Property[] getProperties()
234     {
235         if (arProperties == null)
236         {
237             Collection values= _nameToPropertyMap.values();
238                 arProperties= (Property[]) values.toArray(new Property[_nameToPropertyMap.size()]);
239         }
240         return arProperties;
241     }
242 
243     /** Stores a Property object so that it can be retrieved subsequently by
244      *  {@link #getProperty(String)},{@link #getProperties()},{@link #getPropertyByHandle(int)}.
245      *  Override this method if you want to implement your own mapping from handles
246      *  to Property objects and names to Property objects. Then you also need to override {@link #initMappings},
247      *  {@link #getProperty(String)},{@link #getProperties()},{@link #getPropertyByHandle(int)}.
248      *  @param prop The Property object that is to be stored.
249      */
putProperty(Property prop)250     protected void putProperty(Property prop)
251     {
252         _nameToPropertyMap.put(prop.Name, prop);
253         if (prop.Handle != -1)
254             _handleToPropertyMap.put(new Integer(prop.Handle), prop);
255     }
256 
257     /** Assigns an identifyer object to a Property object so that the identifyer
258      *  can be obtained by {@link #getPropertyId getPropertyId} later on. The identifyer
259      *  is used to specify a certain storage for the property's value. If you do not
260      *  override {@link #setPropertyValueNoBroadcast setPropertyValueNoBroadcast} or {@link #getPropertyValue(Property)}
261      *  then the argument <em>id</em> has to be a java.lang.String that equals the name of
262      *  the member variable that holds the Property's value.
263      *  Override this method if you want to implement your own mapping from Property objects  to ids or
264      *  if you need ids of a type other then java.lang.String.
265      *  Then you also need to override {@link #initMappings initMappings} and {@link #getPropertyId getPropertyId}.
266      *  @param prop The Property object that is being assigned an id.
267      *  @param id The object which identifies the storage used for the property's value.
268      *  @see #registerProperty(Property, Object)
269      */
assignPropertyId(Property prop, Object id)270     protected void assignPropertyId(Property prop, Object id)
271     {
272        if (id instanceof String && ((String) id).equals("") == false)
273             _propertyToIdMap.put(prop, id);
274     }
275 
276     /** Returns the identifyer object for a certain Property. The object must have been
277      *  previously assigned to the Property object by {@link #assignPropertyId assignPropertyId}.
278      *  Override this method if you want to implement your own mapping from Property objects to ids.
279      *  Then you also need to override {@link #initMappings initMappings} and {@link #assignPropertyId assignPropertyId}.
280      *  @param prop The property for which the id is to be retrieved.
281      *  @return The id object that identifies the storage used for the property's value.
282      *  @see #registerProperty(Property, Object)
283      */
getPropertyId(Property prop)284     protected Object getPropertyId(Property prop)
285     {
286         return _propertyToIdMap.get(prop);
287     }
288 
289     /** Initializes data structures used for mappings of property names to property object,
290      *  property handles to property objects and property objects to id objects.
291      *  Override this method if you want to implement your own mappings. Then you also need to
292      *  override {@link #putProperty putProperty},{@link #getProperty getProperty}, {@link #getPropertyByHandle},
293      *  {@link #assignPropertyId assignPropertyId} and {@link #getPropertyId getPropertyId}.
294      */
initMappings()295     protected void initMappings()
296     {
297        _nameToPropertyMap= new HashMap();
298        _handleToPropertyMap= new HashMap();
299        _propertyToIdMap= new HashMap();
300     }
301 
302     /** Makes sure that listeners which are kept in aBoundLC (XPropertyChangeListener) and aVetoableLC
303      *  (XVetoableChangeListener) receive a disposing call. Also those listeners are relesased.
304      */
postDisposing()305     protected void postDisposing()
306     {
307         // Create an event with this as sender
308     	EventObject aEvt= new EventObject(this);
309 
310         // inform all listeners to reelease this object
311     	aBoundLC.disposeAndClear(aEvt);
312         aVetoableLC.disposeAndClear(aEvt);
313     }
314 
315     //XPropertySet ----------------------------------------------------
addPropertyChangeListener(String str, XPropertyChangeListener xPropertyChangeListener)316     synchronized public void addPropertyChangeListener(String str, XPropertyChangeListener xPropertyChangeListener)
317     throws UnknownPropertyException, WrappedTargetException
318     {
319   		// only add listeners if you are not disposed
320         if (! bInDispose && ! bDisposed)
321         {
322             if (str.length() > 0)
323             {
324                 Property prop= getProperty(str);
325                 if (prop == null)
326                     throw new UnknownPropertyException("Property " + str + " is unknown");
327 
328                 // Add listener for a certain property
329                 if ((prop.Attributes & PropertyAttribute.BOUND) > 0)
330                     aBoundLC.addInterface(str, xPropertyChangeListener);
331                 else
332                     //ignore silently
333                     return;
334             }
335             else
336                 // Add listener for all properties
337                 listenerContainer.addInterface(XPropertyChangeListener.class, xPropertyChangeListener);
338         }
339     }
340     //XPropertySet ----------------------------------------------------
addVetoableChangeListener(String str, com.sun.star.beans.XVetoableChangeListener xVetoableChangeListener)341     synchronized public void addVetoableChangeListener(String str, com.sun.star.beans.XVetoableChangeListener xVetoableChangeListener) throws com.sun.star.beans.UnknownPropertyException, com.sun.star.lang.WrappedTargetException
342     {
343  		// only add listeners if you are not disposed
344         if (! bInDispose && ! bDisposed)
345         {
346             if (str.length() > 0)
347             {
348                 Property prop= getProperty(str);
349                 if (prop == null)
350                     throw new UnknownPropertyException("Property " + str + " is unknown");
351 
352                 // Add listener for a certain property
353                 if ((prop.Attributes & PropertyAttribute.CONSTRAINED) > 0)
354                     aVetoableLC.addInterface(str, xVetoableChangeListener);
355                 else
356                     //ignore silently
357                     return;
358             }
359             else
360                 // Add listener for all properties
361                 listenerContainer.addInterface(XVetoableChangeListener.class, xVetoableChangeListener);
362         }
363     }
364     //XPropertySet ----------------------------------------------------
getPropertySetInfo()365     public com.sun.star.beans.XPropertySetInfo getPropertySetInfo()
366     {
367         if (propertySetInfo == null)
368         {
369             synchronized (this)
370             {
371                 if (propertySetInfo == null)
372                     propertySetInfo= new PropertySetInfo();
373             }
374         }
375         return propertySetInfo;
376     }
377     //XPropertySet ----------------------------------------------------
getPropertyValue(String name)378     public Object getPropertyValue(String name) throws UnknownPropertyException, WrappedTargetException
379     {
380         Object ret= null;
381         if (bInDispose || bDisposed)
382             throw new com.sun.star.lang.DisposedException("The component has been disposed already");
383 
384         Property prop= getProperty(name);
385         if (prop == null)
386             throw new UnknownPropertyException("The property " + name + " is unknown");
387 
388         synchronized (this)
389         {
390             ret= getPropertyValue(prop);
391         }
392         // null must not be returned. Either a void any is returned or an any containing
393         // an interface type and a null reference.
394         if (ret == null)
395         {
396             if (prop.Type.getTypeClass() == TypeClass.INTERFACE)
397                 ret= new Any(prop.Type, null);
398             else
399                 ret= new Any(new Type(void.class), null);
400         }
401         return ret;
402     }
403 
404     //XPropertySet ----------------------------------------------------
removePropertyChangeListener(String propName, XPropertyChangeListener listener)405     synchronized public void removePropertyChangeListener(String propName, XPropertyChangeListener listener) throws UnknownPropertyException, WrappedTargetException
406     {	// all listeners are automaticly released in a dispose call
407         if (!bInDispose && !bDisposed)
408         {
409             if (propName.length() > 0)
410             {
411                 Property prop = getProperty(propName);
412                 if (prop == null)
413                     throw new UnknownPropertyException("Property " + propName + " is unknown");
414                 aBoundLC.removeInterface(propName, listener);
415             }
416             else
417                 listenerContainer.removeInterface(XPropertyChangeListener.class, listener);
418         }
419     }
420 
421     //XPropertySet ----------------------------------------------------
removeVetoableChangeListener(String propName, XVetoableChangeListener listener)422     synchronized public void removeVetoableChangeListener(String propName, XVetoableChangeListener listener) throws UnknownPropertyException, WrappedTargetException
423     {// all listeners are automaticly released in a dispose call
424         if (!bInDispose && !bDisposed)
425         {
426             if (propName.length() > 0)
427             {
428                 Property prop = getProperty(propName);
429                 if (prop == null)
430                     throw new UnknownPropertyException("Property " + propName + " is unknown");
431                 aVetoableLC.removeInterface(propName, listener);
432             }
433             else
434                 listenerContainer.removeInterface(XVetoableChangeListener.class, listener);
435         }
436     }
437 
438     //XPropertySet ----------------------------------------------------
439     /** Sets the value of a property.
440      *  The idl description for this interfaces, stipulates that the argument value is an Any. Since a java.lang.Object
441      *  reference has the same meaning as an Any this function accepts
442      *  java anys (com.sun.star.uno.Any) and all other appropriate objects as arguments. The value argument can be one
443      *  of these:
444      *  <ul>
445      *  <li>java.lang.Boolean</li>
446      *  <li>java.lang.Character</li>
447      *  <li>java.lang.Byte</li>
448      *  <li>java.lang.Short</li>
449      *  <li>java.lang.Integer</li>
450      *  <li>java.lang.Long</li>
451      *  <li>java.lang.Float</li>
452      *  <li>java.lang.Double</li>
453      *  <li>java.lang.String</li>
454      *  <li>com.sun.star.uno.Type</li>
455      *  <li><em>objects which implement UNO interfaces</em></li>
456      *  <li><em>arrays which contain elements of the types above</em></li>
457      *  <li>com.sun.star.uno.Any containing an instance of one of the above types</li>
458      *  </ul>
459      *
460      *  Properties can have the attribute com.sun.star.beans.PropertyAttribute.MAYBEVOID, which means that the value
461      *  (not the type) can be void. In order to assign a void value to a property one can either pass an Any which
462      *  contains a null reference or pass null directly. In bothe cases the null reference is only accepted if
463      *  the PropertyAttribute.MAYBEVOID attribute is set for the property.
464      *
465      *  Properties which have the attribute MAYBEVOID set (Property.Attributes) can have a void value. The following
466      *  considerations presume that the Property has that attribute set. Further, when mentioning an Any's value we
467      *  actually refer to the object returned by Any.getObject.
468      *  If the argument <em>value</em> is null, or it is an Any whose value is null (but with a valid Type)
469      *  then the member variable used for storing the property's value is set to null.
470      *  Therefore those properties can only be stored in objects
471      *  and primitive types are not allowed (one can use the wrapper classes instead,e.g. java.lang.Byte) .
472      *  If a property's value is kept in a member variable of type Any and that reference is still null
473      *  then when setPropertyValue is called with
474      *  <em>value</em> = null then the member variable is assigned an Any with type void and a null value.
475      *  Or if the argument is an Any with a null value then it is assigned to the member variable.
476      *  Further, if the variable already
477      *  references an Any and setPropertyValue is called with <em>value</em> = null, then the variable is assigned
478      *  a new Any with the same type as the previously referenced Any and with a null value.
479      *  @param name The name of the property.
480      *  @param value The new value of the property.
481      *     *     */
setPropertyValue(String name, Object value)482     public void setPropertyValue(String name, Object value) throws UnknownPropertyException,
483     PropertyVetoException, com.sun.star.lang.IllegalArgumentException,  WrappedTargetException
484     {
485         Property prop= getProperty(name);
486         if (prop == null)
487             throw new UnknownPropertyException("Property " + name + " is unknown");
488         setPropertyValue(prop, value);
489     }
490 
491     /** Sets the value of a property. It checks if the property's attributes (READONLY,MAYBEVOID), allow that the
492      *  new value can be set. It also causes the notification of listeners.
493      *  @param prop The property whose value is to be set.
494      *  @param value The new value for the property.
495      */
setPropertyValue(Property prop, Object value)496     protected void setPropertyValue(Property prop, Object value) throws UnknownPropertyException,
497     PropertyVetoException, com.sun.star.lang.IllegalArgumentException, WrappedTargetException
498     {
499         if ((prop.Attributes & PropertyAttribute.READONLY) == PropertyAttribute.READONLY)
500             throw new com.sun.star.beans.PropertyVetoException();
501         // The value may be null only if MAYBEVOID attribute is set
502         boolean bVoidValue= false;
503         if (value instanceof Any)
504             bVoidValue= ((Any) value).getObject() == null;
505         else
506             bVoidValue= value == null;
507         if (bVoidValue && (prop.Attributes & PropertyAttribute.MAYBEVOID) == 0)
508             throw new com.sun.star.lang.IllegalArgumentException("The property must have a value; the MAYBEVOID attribute is not set!");
509         if (bInDispose || bDisposed)
510             throw new DisposedException("Component is already disposed");
511 
512         //Check if the argument is allowed
513         boolean bValueOk= false;
514         if (value instanceof Any)
515             bValueOk= checkType(((Any) value).getObject());
516         else
517             bValueOk= checkType(value);
518         if (! bValueOk)
519             throw new com.sun.star.lang.IllegalArgumentException("No valid UNO type");
520 
521 
522         boolean bConversionOk= false;
523         Object[] outConvertedVal= new Object[1];
524         Object[] outOldValue= new Object[1];
525         synchronized (this)
526         {
527             bConversionOk= convertPropertyValue(prop, outConvertedVal, outOldValue, value);
528         }
529 
530         //The next step following the conversion is to set the new value of the property. Prior to this
531         // the XVetoableChangeListener s have to be notified.
532         if (bConversionOk)
533         {
534             // If the property is CONSTRAINED, then we must notify XVetoableChangeListener. The listener can throw a com.sun.star.lang.beans.PropertyVetoException which
535             // will cause this method to return (the exception is not caught here).
536             fire( new Property[]{prop}, outConvertedVal, outOldValue, true);
537 
538             synchronized (this)
539             {
540                 setPropertyValueNoBroadcast(prop, outConvertedVal[0]);
541             }
542             // fire a change event (XPropertyChangeListener, PropertyAttribute.BOUND
543             fire( new Property[]{prop}, outConvertedVal, outOldValue, false);
544         }
545     }
546 
547     /** Converts a value in a way so that it is appropriate for storing as a property value, that is
548      *  {@link #setPropertyValueNoBroadcast setPropertyValueNoBroadcast} can process the value without any further
549      *  conversion. This implementation presumes that
550      *  the values are stored in member variables of the furthest inheriting class. For example,
551      *  class A inherits this class then members of class A
552      *  can hold property values. If there is a class B which inherits A then only members of B can hold
553      *  property values. The variables must be public. A property must have been registered (e.g. by
554      *  {@link #registerProperty(Property, Object)} in order for this method to work. The identifyer argument (type Object)
555      *  used in the registerProperty methods must
556      *  be a java.lang.String, which is, the name of the member variable that holds the property value.
557      *  If one opts to store values differently then one may override
558      *  this method, as well as {@link #setPropertyValueNoBroadcast setPropertyValueNoBroadcast} and
559      *  {@link #getPropertyValue(Property) getPropertyValue(Property)}.
560      *  This method is always called as a result of a call to one of the setter methods, such as
561      *  {@link #setPropertyValue(String,Object) XPropertySet.setPropertyValue},
562      *  {@link #setFastPropertyValue XFastPropertySet.setFastPropertyValue}
563      *  and {@link #setPropertyValues XMultiPropertySet.setPropertyValues}.
564      *  If this method fails, that is, it returns false or throws an exception, then no listeners are notified and the
565      *  property value, that was intended to be changed, remains untouched.<br> This method does not have to deal with property attributes, such as
566      *  PropertyAttribute.READONLY or PropertyAttribute.MAYBEVOID. The processing of these attributes occurs
567      *  in the calling methods.<br>
568      *  Only if this method returns successfully further processing, such
569      *  as listener notification and finally the modifiction of the property's value, will occur.<br>
570      *
571      *  The actual modification of a property's value is done by {@link #setPropertyValueNoBroadcast setPropertyValueNoBroadcast}
572      *  which is called subsequent to convertPropertyValue.
573      *<p>
574      *  This method converts values by help of the com.sun.star.uno.AnyConverter which only does a few widening
575      *  conversions on integer types and floating point types. For example, there is the property PropA with a Type equivalent
576      *  to int.class and the
577      *  value of the property is to be stored in a member variable of type int with name intProp. Then setPropertyValue is
578      *  called:
579      *  <pre>
580      *  set.setPropertyValue( "PropA", new Byte( (byte)111));
581      *  </pre>
582      *  At some point setPropertyValue will call convertPropertyValue and pass in the Byte object. Since we allow
583      *  that Byte values can be used with the property and know that the value is to be stored in intProp (type int)
584      *  we convert the Byte object into an Integer object which is then returned in the out-parameter <em>newVal</em>. This
585      *  conversion is actually performed by the AnyConverter. Later
586      *  the setPropertyValueNoBroadcast is called with that Integer object and the int value can be easily extracted
587      *  from the object and be assigned to the member intProp.
588      *  <p>
589      *  The method handles Any arguments the same as Object arguments. That is, the <em>setVal</em> argument can
590      *  be a java.lang.Boolean or a com.sun.star.uno.Any containing a java.lang.Boolean. Likewise, a member
591      *  containing a property value can be a com.sun.star.uno.Any or an java.lang.Object.
592      *  Then, no conversion is necessary, since they can hold all possible values. However, if
593      *  the member is an Object and <em>setVal</em> is an Any then the object contained in the any is assigned to
594      *  the member. The extra type information which exists as Type object in the Any will get lost. If this is not
595      *  intended then use an Any variable rather then an Object.<br>
596      *  If a member is an Object or Any and the argument <em>setVal</em> is an Object, other than String or array,
597      *  then it is presumed to be an UNO object and queried for XInterface. If successful, the out-param <em>newVal</em>
598      *  returns the XInterface.<br>
599      *  If a member is an UNO interface, then <em>setVal</em> is queried for this interface and the result is returned.
600      *  If <em>setVal</em> is null then <em>newVal</em> will be null too after return.
601      *  <p>
602      *  If a property value is stored using a primitive type the the out-parameters
603      *  <em>curVal</em> and <em>newVal</em> contain the respective wrapper class (e.g.java.lang.Byte, etc.).
604      *  curVal is used in calls to the XVetoableChangeListener and XPropertyChangeListener.
605      *
606      * @param property - in-param property for which the data is to be converted.
607      * @param newVal - out-param which contains the converted value on return.
608      * @param curVal - out-param the current value of the property. It is used in calls to the
609      *                   XVetoableChangeListener and XPropertyChangeListener.
610      *  @param setVal - in-param. The value that is to be converted so that it matches Property and the internally used
611      *  dataformat for that property.
612      *  @return true - Conversion was successful. <em>newVal</em> contains a valid value for the property. false -
613      *  conversion failed for some reason.
614      *  @throws com.sun.star.lang.IllegalArgumentException The value provided is unfit for the property.
615      *  @throws com.sun.star.lang.WrappedTargetException - An exception occured during the conversion, that is to be made known
616      *  to the caller.
617      */
convertPropertyValue(Property property, Object[] newVal, Object[]curVal, Object setVal)618     protected boolean convertPropertyValue(Property property, Object[] newVal, Object[]curVal,  Object setVal)
619         throws com.sun.star.lang.IllegalArgumentException, WrappedTargetException, UnknownPropertyException
620     {
621         boolean ret= true;
622         try
623         {
624             // get the member name
625             String sMember= (String) getPropertyId(property);
626             if (sMember != null)
627             {
628                 // use reflection to obtain the field that holds the property value
629                 // Class.getDeclaredFields does not return inherited fields. One could use Class.getFields to
630                 // also get inherited fields, but only those which are public.
631                 Field propField= getClass().getDeclaredField(sMember);
632                 if (propField != null)
633                 {
634                     curVal[0]= propField.get(this);
635                     Class memberClass= propField.getType();
636 
637                     // MAYBEVOID: if setVal == null or it is an Any and getObject returns null, then a void value is to be set
638                     // This works only if there are no primitive types. For those we use the respective wrapper classes.
639                     // In this implementation, a null reference means void value.
640                     boolean bVoidValue= false;
641                     boolean bAnyVal= setVal instanceof Any;
642                     if (bAnyVal)
643                         bVoidValue= ((Any) setVal).getObject() == null;
644                     else
645                         bVoidValue= setVal == null;
646                     if (bVoidValue && memberClass.isPrimitive())
647                         throw new com.sun.star.lang.IllegalArgumentException("The implementation does not support the MAYBEVOID attribute for this property");
648 
649                     Object convObj= null;
650                     //The member that keeps the value of the Property is an Any. It can contain all possible
651                     //types, therefore a conversion is not necessary.
652                     if (memberClass.equals(Any.class))
653                     {
654                         if (bAnyVal)
655                             //parameter setVal is also an Any and can be used without further processing
656                             convObj= setVal;
657                         else
658                         {
659                             // Parameter setVal is not an Any. We need to construct an Any that contains
660                             // the argument setVal.
661                             // If setVal is an interface implementation then, we cannot constuct the
662                             // Any with setVal.getClass(), because the Any.Type._typeClass would be TypeClass.UNKNOWN.
663                             // We try to get an XInterface of setVal and set an XInterface type.
664                             if (setVal instanceof XInterface)
665                             {
666                                 XInterface xint= UnoRuntime.queryInterface(XInterface.class, setVal);
667                                 if (xint != null)
668                                     convObj= new Any(new Type(XInterface.class), xint);
669                             }
670                             // The member is an any, and the past in argument was null reference (MAYBEVOID is set)
671                             else if (setVal == null)
672                             {
673                                 // if the any member is still null we create a void any
674                                 if (curVal[0] == null)
675                                     convObj= new Any(new Type(), null);
676                                 else
677                                 {
678                                     //otherwise we create an Any with the same type as a value of null;
679                                     convObj= new Any( ((Any)curVal[0]).getType(), null);
680                                 }
681                             }
682                             else
683                                 convObj= new Any(new Type(setVal.getClass()), setVal);
684                         }
685                     }
686                     else
687                         convObj= convert(memberClass, setVal);
688                     newVal[0]= convObj;
689                 }
690             }
691             else
692                 throw new UnknownPropertyException("Property " + property.Name + " is unknown");
693         }
694         catch (java.lang.NoSuchFieldException e)
695         {
696             throw new WrappedTargetException("Field does not exist", this, e);
697         }
698         catch (java.lang.IllegalAccessException e)
699         {
700             throw new WrappedTargetException("", this ,e);
701         }
702         return ret;
703     }
704 
checkType(Object obj)705     private boolean checkType(Object obj)
706     {
707         if (obj == null
708         || obj instanceof Boolean
709         || obj instanceof Character
710         || obj instanceof Number
711         || obj instanceof String
712         || obj instanceof XInterface
713         || obj instanceof Type
714         || obj instanceof com.sun.star.uno.Enum
715         || obj.getClass().isArray())
716             return true;
717         return false;
718     }
719 
720     // Param object can be an Any or other object. If obj is null then the return value is null
convert( Class cl, Object obj)721     private Object convert( Class cl, Object obj) throws com.sun.star.lang.IllegalArgumentException
722     {
723         Object retVal= null;
724        //The member that keeps the value of the Property is an Object.Objects are similar to Anys in that they can
725        // hold all types.
726         if (obj == null || (obj instanceof Any && ((Any) obj).getObject() == null))
727             retVal= null;
728         else if(cl.equals(Object.class))
729         {
730             if (obj instanceof Any)
731                 obj= ((Any) obj).getObject();
732             retVal= obj;
733         }
734         else if(cl.equals(boolean.class))
735             retVal= new Boolean(AnyConverter.toBoolean(obj));
736         else if (cl.equals(char.class))
737             retVal= new Character(AnyConverter.toChar(obj));
738         else if (cl.equals(byte.class))
739             retVal= new Byte(AnyConverter.toByte(obj));
740         else if (cl.equals(short.class))
741             retVal= new Short(AnyConverter.toShort(obj));
742         else if (cl.equals(int.class))
743             retVal= new Integer(AnyConverter.toInt(obj));
744         else if (cl.equals(long.class))
745             retVal= new Long(AnyConverter.toLong(obj));
746         else if (cl.equals(float.class))
747             retVal= new Float(AnyConverter.toFloat(obj));
748         else if (cl.equals(double.class))
749             retVal= new Double(AnyConverter.toDouble(obj));
750         else if (cl.equals(String.class))
751             retVal= AnyConverter.toString(obj);
752         else if (cl.isArray())
753             retVal= AnyConverter.toArray(obj);
754         else if (cl.equals(Type.class))
755             retVal= AnyConverter.toType(obj);
756         else if (cl.equals(Boolean.class))
757             retVal= new Boolean(AnyConverter.toBoolean(obj));
758         else if (cl.equals(Character.class))
759             retVal= new Character(AnyConverter.toChar(obj));
760         else if (cl.equals(Byte.class))
761             retVal= new Byte(AnyConverter.toByte(obj));
762         else if (cl.equals(Short.class))
763             retVal= new Short(AnyConverter.toShort(obj));
764         else if (cl.equals(Integer.class))
765             retVal= new Integer(AnyConverter.toInt(obj));
766         else if (cl.equals(Long.class))
767             retVal= new Long(AnyConverter.toLong(obj));
768         else if (cl.equals(Float.class))
769             retVal= new Float(AnyConverter.toFloat(obj));
770         else if (cl.equals(Double.class))
771             retVal= new Double(AnyConverter.toDouble(obj));
772         else if (XInterface.class.isAssignableFrom(cl))
773             retVal= AnyConverter.toObject(new Type(cl), obj);
774         else if (com.sun.star.uno.Enum.class.isAssignableFrom(cl))
775             retVal= AnyConverter.toObject(new Type(cl), obj);
776         else
777             throw new com.sun.star.lang.IllegalArgumentException("Could not convert the argument");
778         return retVal;
779     }
780 
781     /**  Sets the value of a property. In this implementation property values are stored in member variables
782      *  (see {@link #convertPropertyValue convertPropertyValue} Notification of property listeners
783      *  does not occur in this method. By overriding this method one can take full control about how property values
784      *  are stored. But then, the {@link #convertPropertyValue convertPropertyValue} and
785      *  {@link #getPropertyValue(Property)} must be overridden too.
786      *
787      *  A Property with the MAYBEVOID attribute set, is stored as null value. Therefore the member variable must be
788      *  an Object in order to make use of the property attribute. An exception is Any. The Any variable can be initially null, but
789      *  once it is set the reference will not become null again. If the value is to be set to
790      *  void then a new Any will be stored
791      *  with a valid type but without a value (i.e. Any.getObject returns null).
792      *  If a property has the READONLY attribute set, and one of the setter methods, such as setPropertyValue, has been
793      *  called, then this method is not going to be called.
794      *  @param property the property for which the new value is set
795      *  @param newVal the new value for the property.
796      *  @throws com.sun.star.lang.WrappedTargetException An exception, which has to be made known to the caller,
797      *  occured during the setting of the value.
798      */
setPropertyValueNoBroadcast(Property property, Object newVal)799     protected void setPropertyValueNoBroadcast(Property property, Object newVal)
800     throws WrappedTargetException
801     {
802         try
803         {
804             // get the member name
805             String sMember= (String) getPropertyId(property);
806             if (sMember != null)
807             {
808                 // use reflection to obtain the field that holds the property value
809                 // Class.getDeclaredFields does not return inherited fields. One could use Class.getFields to
810                 // also get inherited fields, but only those which are public.
811                 Field propField= getClass().getDeclaredField(sMember);
812                 if (propField != null)
813                     propField.set(this, newVal);
814             }
815         }
816         catch(java.lang.Exception e)
817         {
818             throw new WrappedTargetException("PropertySet.setPropertyValueNoBroadcast", this, e);
819         }
820     }
821     /** Retrieves the value of a property. This implementation presumes that the values are stored in member variables
822      *  of the furthest inheriting class (see {@link #convertPropertyValue convertPropertyValue}) and that the
823      *  variables are public. The property must have
824      *  been registered, for example by {@link #registerProperty(Property, Object)}. The identifyer Object argument
825      *  must have been a java.lang.String which was the name of the member variable holding the property value.
826      *  When properties are to be stored differently one has to override this method as well as
827      *  {@link #convertPropertyValue} and {@link #setPropertyValueNoBroadcast}. <br>
828      *  If a value is stored in a variable of a primitive type then this method returns an instance of the respective
829      *  wrapper class (e.g. java.lang.Boolean).
830      *  @param property The property for which the value is to be retrieved.
831      *  @return The value of the property.
832      */
getPropertyValue(Property property)833     protected Object getPropertyValue(Property property)
834     {
835         Object ret= null;
836         try
837         {
838             // get the member name
839             String sMember= (String) getPropertyId(property);
840             if (sMember != null)
841             {
842                 // use reflection to obtain the field that holds the property value
843                 // Class.getDeclaredFields does not return inherited fields. One could use Class.getFields to
844                 // also get inherited fields, but only those which are public.
845                 Field propField= getClass().getDeclaredField(sMember);
846                 if (propField != null)
847                     ret= propField.get(this);
848             }
849         }
850         catch(java.lang.NoSuchFieldException e)
851         {
852             throw new java.lang.RuntimeException(e);
853         }
854         catch(java.lang.IllegalAccessException e)
855         {
856             throw new java.lang.RuntimeException(e);
857         }
858         return ret;
859     }
860 
861     /**
862      *  This method fires events to XPropertyChangeListener,XVetoableChangeListener and
863      *  XPropertiesChangeListener event sinks.
864      *  To distinguish what listeners are to be called the argument <em>bVetoable</em> is to be set to true if
865      *  a XVetoableChangeListener is meant. For XPropertyChangeListener and XPropertiesChangeListener
866      *  it is to be set to false.
867      *
868      * @param properties	Properties wich will be or have been affected.
869      * @param newValues	the new values of the properties.
870      * @param oldValues	the old values of the properties.
871      * @param bVetoable true means fire to VetoableChangeListener, false means fire to
872      * XPropertyChangedListener and XMultiPropertyChangedListener.
873      */
fire( Property[] properties, Object[] newValues, Object[] oldValues, boolean bVetoable )874     protected void  fire(
875     Property[]  properties,
876     Object[] newValues,
877     Object[] oldValues,
878     boolean bVetoable ) throws PropertyVetoException
879     {
880         // Only fire, if one or more properties changed
881         int nNumProps= properties.length;
882         if (nNumProps > 0)
883         {
884             PropertyChangeEvent[] arEvts= new PropertyChangeEvent[nNumProps];
885             int nAffectedProps= 0;
886             // Loop over all changed properties to fill the event struct
887             for (int i= 0; i < nNumProps; i++)
888             {
889                 if ((bVetoable && (properties[i].Attributes & PropertyAttribute.CONSTRAINED) > 0)
890                     || (!bVetoable && (properties[i].Attributes & PropertyAttribute.BOUND) > 0))
891                 {
892                     arEvts[i]= new PropertyChangeEvent(this, properties[i].Name, false,
893                                         properties[i].Handle, oldValues[i], newValues[i]);
894                     nAffectedProps++;
895                 }
896             }
897     		// fire the events for all changed properties
898             for (int i= 0; i < nAffectedProps; i++)
899             {
900     			// get the listener container for the property name
901                 InterfaceContainer lc= null;
902                 if (bVetoable)
903                     lc= aVetoableLC.getContainer(arEvts[i].PropertyName);
904                 else
905                     lc= aBoundLC.getContainer(arEvts[i].PropertyName);
906                 if (lc != null)
907                 {
908                     Iterator it= lc.iterator();
909                     while( it.hasNext())
910                     {
911                         Object listener= it.next();
912                         if (bVetoable)
913                             ((XVetoableChangeListener) listener).vetoableChange(arEvts[i]);
914                         else
915                             ((XPropertyChangeListener) listener).propertyChange(arEvts[i]);
916                     }
917                 }
918        			// broadcast to all listeners with "" property name
919         		if(bVetoable)
920                     lc= listenerContainer.getContainer(XVetoableChangeListener.class);
921         		else
922             		lc= listenerContainer.getContainer(XPropertyChangeListener.class);
923     			if(lc != null)
924                 {
925     				Iterator it= lc.iterator();
926                     while(it.hasNext() )
927                     {
928                         Object listener= it.next();
929                         if( bVetoable ) // fire change Events?
930                             ((XVetoableChangeListener) listener).vetoableChange(arEvts[i]);
931                         else
932                             ((XPropertyChangeListener) listener).propertyChange(arEvts[i]);
933                     }
934                 }
935             }
936             // fire at XPropertiesChangeListeners
937             // if nAffectedProps == 0 then there are no BOUND properties
938             if (!bVetoable && nAffectedProps > 0)
939             {
940 
941                 PropertyChangeEvent[] arReduced= new PropertyChangeEvent[nAffectedProps];
942                 System.arraycopy(arEvts, 0, arReduced, 0, nAffectedProps);
943                 InterfaceContainer lc= listenerContainer.getContainer(XPropertiesChangeListener.class);
944     			if (lc != null)
945         		{
946                     Iterator it= lc.iterator();
947                     while (it.hasNext())
948                     {
949                         XPropertiesChangeListener listener = (XPropertiesChangeListener) it.next();
950     					// fire the hole event sequence to the XPropertiesChangeListener's
951                         listener.propertiesChange( arEvts );
952         			}
953             	}
954             }
955         }
956     }
957     // XFastPropertySet--------------------------------------------------------------------------------
setFastPropertyValue(int nHandle, Object aValue )958     public void setFastPropertyValue(int nHandle, Object aValue ) throws UnknownPropertyException,
959     PropertyVetoException, com.sun.star.lang.IllegalArgumentException, WrappedTargetException
960     {
961         Property prop= getPropertyByHandle(nHandle);
962         if (prop == null)
963             throw new UnknownPropertyException(" The property with handle : " + nHandle +" is unknown");
964         setPropertyValue(prop, aValue);
965     }
966 
967     // XFastPropertySet --------------------------------------------------------------------------------
getFastPropertyValue(int nHandle )968     public Object getFastPropertyValue(int nHandle ) throws UnknownPropertyException,
969     WrappedTargetException
970     {
971         Property prop= getPropertyByHandle(nHandle);
972         if (prop == null)
973             throw new UnknownPropertyException("The property with handle : " + nHandle + " is unknown");
974         return getPropertyValue(prop);
975     }
976 
977     // XMultiPropertySet -----------------------------------------------------------------------------------
addPropertiesChangeListener(String[] propNames, XPropertiesChangeListener listener)978     public void addPropertiesChangeListener(String[] propNames, XPropertiesChangeListener listener)
979     {
980         listenerContainer.addInterface(XPropertiesChangeListener.class, listener);
981     }
982 
983     // XMultiPropertySet -----------------------------------------------------------------------------------
firePropertiesChangeEvent(String[] propNames, XPropertiesChangeListener listener)984     public void firePropertiesChangeEvent(String[] propNames, XPropertiesChangeListener listener)
985     {
986         // Build the events.
987         PropertyChangeEvent[] arEvents= new PropertyChangeEvent[propNames.length];
988         int eventCount= 0;
989         // get a snapshot of the current property values
990         synchronized (this)
991         {
992             for (int i= 0; i < propNames.length; i++)
993             {
994                 Property prop= getProperty(propNames[i]);
995                 if (prop != null)
996                 {
997                     Object value= null;
998                     try
999                     {
1000                        value= getPropertyValue(prop);
1001                     }
1002                     catch(Exception e)
1003                     {
1004                         continue;
1005                     }
1006                     arEvents[eventCount]= new PropertyChangeEvent(this, prop.Name,
1007                                         false, prop.Handle, value, value);
1008                     eventCount++;
1009                 }
1010             }
1011         }
1012 
1013         // fire events from unsynchronized section so as to prevent deadlocks
1014         if (eventCount > 0)
1015         {
1016             // Reallocate the array of the events if necessary
1017             if (arEvents.length != eventCount)
1018             {
1019                 PropertyChangeEvent[] arPropsTmp= new PropertyChangeEvent[eventCount];
1020                 System.arraycopy(arEvents, 0, arPropsTmp, 0, eventCount);
1021                 arEvents= arPropsTmp;
1022             }
1023             listener.propertiesChange(arEvents);
1024         }
1025     }
1026     // XMultiPropertySet -----------------------------------------------------------------------------------
1027     /** If a value for a property could not be retrieved then the respective element in the returned
1028      *  array has the value null.
1029      */
getPropertyValues(String[] propNames)1030     public Object[] getPropertyValues(String[] propNames)
1031     {
1032         Object[] arValues= new Object[propNames.length];
1033         synchronized (this)
1034         {
1035             for (int i= 0; i < propNames.length; i++)
1036             {
1037                 Object value= null;
1038                 try
1039                 {
1040                     value= getPropertyValue(propNames[i]);
1041                 }
1042                 catch (Exception e)
1043                 {
1044                 }
1045                 arValues[i]= value;
1046             }
1047         }
1048         return arValues;
1049     }
1050     // XMultiPropertySet -----------------------------------------------------------------------------------
removePropertiesChangeListener(XPropertiesChangeListener xPropertiesChangeListener)1051     public void removePropertiesChangeListener(XPropertiesChangeListener xPropertiesChangeListener)
1052     {
1053         listenerContainer.removeInterface(XPropertiesChangeListener.class, xPropertiesChangeListener);
1054     }
1055     // XMultiPropertySet -----------------------------------------------------------------------------------
1056     /** If the array of property names containes an unknown property then it will be ignored.
1057      */
setPropertyValues(String[] propNames, Object[] values)1058     public void setPropertyValues(String[] propNames, Object[] values) throws PropertyVetoException, com.sun.star.lang.IllegalArgumentException, com.sun.star.lang.WrappedTargetException
1059     {
1060         for (int i= 0; i < propNames.length; i++)
1061         {
1062             try
1063             {
1064                 setPropertyValue(propNames[i], values[i]);
1065             }
1066             catch (UnknownPropertyException e)
1067             {
1068                 continue;
1069             }
1070 
1071         }
1072     }
1073 
1074     private class PropertySetInfo implements XPropertySetInfo
1075     {
getProperties()1076         public com.sun.star.beans.Property[] getProperties()
1077         {
1078             return PropertySet.this.getProperties();
1079         }
1080 
getPropertyByName(String name)1081         public com.sun.star.beans.Property getPropertyByName(String name) throws UnknownPropertyException
1082         {
1083             return getProperty(name);
1084         }
1085 
hasPropertyByName(String name)1086         public boolean hasPropertyByName(String name)
1087         {
1088             return getProperty(name) != null;
1089         }
1090 
1091     }
1092 }
1093 
1094 
1095 
1096 
1097 
1098