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