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 package org.apache.openoffice.comp.sdbc.dbtools.comphelper;
23 
24 import java.util.Arrays;
25 import java.util.Comparator;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.Map;
29 import java.util.concurrent.atomic.AtomicInteger;
30 
31 import com.sun.star.beans.Property;
32 import com.sun.star.beans.PropertyAttribute;
33 import com.sun.star.beans.PropertyChangeEvent;
34 import com.sun.star.beans.PropertyVetoException;
35 import com.sun.star.beans.UnknownPropertyException;
36 import com.sun.star.beans.XFastPropertySet;
37 import com.sun.star.beans.XMultiPropertySet;
38 import com.sun.star.beans.XPropertiesChangeListener;
39 import com.sun.star.beans.XPropertyChangeListener;
40 import com.sun.star.beans.XPropertySet;
41 import com.sun.star.beans.XPropertySetInfo;
42 import com.sun.star.beans.XVetoableChangeListener;
43 import com.sun.star.lang.EventObject;
44 import com.sun.star.lang.IllegalArgumentException;
45 import com.sun.star.lang.WrappedTargetException;
46 import com.sun.star.lib.uno.helper.InterfaceContainer;
47 import com.sun.star.lib.uno.helper.MultiTypeInterfaceContainer;
48 import com.sun.star.uno.Any;
49 import com.sun.star.uno.AnyConverter;
50 import com.sun.star.uno.Type;
51 import com.sun.star.uno.TypeClass;
52 import com.sun.star.uno.XInterface;
53 
54 public class PropertySetAdapter implements XPropertySet, XFastPropertySet, XMultiPropertySet {
55     private final Object lock;
56     private final Object eventSource;
57     // after registerListeners(), these are read-only:
58     private final Map<String,PropertyData> propertiesByName = new HashMap<String,PropertyData>();
59     private final Map<Integer,PropertyData> propertiesByHandle = new HashMap<Integer,PropertyData>();
60     private AtomicInteger nextHandle = new AtomicInteger(1);
61     // interface containers are locked internally:
62     protected final MultiTypeInterfaceContainer boundListeners = new MultiTypeInterfaceContainer();
63     protected final MultiTypeInterfaceContainer vetoableListeners = new MultiTypeInterfaceContainer();
64     protected final InterfaceContainer propertiesChangeListeners = new InterfaceContainer();
65     private final PropertySetInfo propertySetInfo = new PropertySetInfo();
66 
67     public static interface PropertyGetter {
getValue()68         Object getValue() throws WrappedTargetException;
69     }
70 
71     public static interface PropertySetter {
setValue(Object value)72         void setValue(Object value) throws PropertyVetoException, IllegalArgumentException, WrappedTargetException;
73     }
74 
75     private static class PropertyData {
76         Property property;
77         PropertyGetter getter;
78         PropertySetter setter;
79 
PropertyData(Property property, PropertyGetter getter, PropertySetter setter)80         PropertyData(Property property, PropertyGetter getter, PropertySetter setter) {
81             this.property = property;
82             this.getter = getter;
83             this.setter = setter;
84         }
85     }
86 
87     private static final Comparator<Property> propertyNameComparator = new Comparator<Property>() {
88         @Override
89         public int compare(Property first, Property second) {
90             return first.Name.compareTo(second.Name);
91         }
92     };
93 
94     private class PropertySetInfo implements XPropertySetInfo {
95         @Override
getProperties()96         public Property[] getProperties() {
97             Property[] properties = new Property[propertiesByName.size()];
98             int next = 0;
99             for (Map.Entry<String,PropertyData> entry : propertiesByName.entrySet()) {
100                 properties[next++] = entry.getValue().property;
101             }
102             Arrays.sort(properties, propertyNameComparator);
103             return properties;
104         }
105 
106         @Override
getPropertyByName(String propertyName)107         public Property getPropertyByName(String propertyName) throws UnknownPropertyException {
108             PropertyData propertyData = getPropertyData(propertyName);
109             return propertyData.property;
110         }
111 
112         @Override
hasPropertyByName(String propertyName)113         public boolean hasPropertyByName(String propertyName) {
114             return propertiesByName.containsKey(propertyName);
115         }
116     }
117 
118     /**
119      * Creates a new instance.
120      * @param lock the lock that will be held while calling the getters and setters
121      * @param eventSource the com.sun.star.lang.EventObject Source field, to use in events sent to listeners
122      */
PropertySetAdapter(Object lock, Object eventSource)123     public PropertySetAdapter(Object lock, Object eventSource) {
124         this.lock = lock;
125         this.eventSource = eventSource;
126     }
127 
dispose()128     public void dispose() {
129         // Create an event with this as sender
130         EventObject event = new EventObject(eventSource);
131 
132         // inform all listeners to release this object
133         boundListeners.disposeAndClear(event);
134         vetoableListeners.disposeAndClear(event);
135     }
136 
registerProperty(String propertyName, int handle, Type type, short attributes, PropertyGetter getter, PropertySetter setter)137     public void registerProperty(String propertyName, int handle, Type type, short attributes,
138             PropertyGetter getter, PropertySetter setter) {
139         Property property = new Property(propertyName, handle, type, attributes);
140         PropertyData propertyData = new PropertyData(property, getter, setter);
141         propertiesByName.put(propertyName, propertyData);
142         propertiesByHandle.put(property.Handle, propertyData);
143     }
144 
registerProperty(String propertyName, Type type, short attributes, PropertyGetter getter, PropertySetter setter)145     public void registerProperty(String propertyName, Type type, short attributes,
146             PropertyGetter getter, PropertySetter setter) {
147         int handle;
148         // registerProperty() should only be called from one thread, but just in case:
149         handle = nextHandle.getAndIncrement();
150         registerProperty(propertyName, handle, type, attributes, getter, setter);
151     }
152 
153     @Override
addPropertyChangeListener( String propertyName, XPropertyChangeListener listener)154     public void addPropertyChangeListener(
155             String propertyName, XPropertyChangeListener listener) throws UnknownPropertyException, WrappedTargetException {
156         PropertyData propertyData = getPropertyData(propertyName);
157         if ((propertyData.property.Attributes & PropertyAttribute.BOUND) != 0) {
158             boundListeners.addInterface(propertyName, listener);
159         } // else ignore silently
160     }
161 
162     @Override
addVetoableChangeListener( String propertyName, XVetoableChangeListener listener)163     public void addVetoableChangeListener(
164             String propertyName, XVetoableChangeListener listener) throws UnknownPropertyException, WrappedTargetException {
165         PropertyData propertyData = getPropertyData(propertyName);
166         if ((propertyData.property.Attributes & PropertyAttribute.CONSTRAINED) != 0) {
167             vetoableListeners.addInterface(propertyName, listener);
168         } // else ignore silently
169     }
170 
171     @Override
addPropertiesChangeListener(String[] propertyNames, XPropertiesChangeListener listener)172     public void addPropertiesChangeListener(String[] propertyNames, XPropertiesChangeListener listener) {
173         propertiesChangeListeners.add(listener);
174     }
175 
176     @Override
getPropertySetInfo()177     public XPropertySetInfo getPropertySetInfo() {
178         return propertySetInfo;
179     }
180 
getPropertyData(String propertyName)181     private PropertyData getPropertyData(String propertyName) throws UnknownPropertyException {
182         PropertyData propertyData = propertiesByName.get(propertyName);
183         if (propertyData == null) {
184             throw new UnknownPropertyException(propertyName);
185         }
186         return propertyData;
187     }
188 
getPropertyData(int handle)189     private PropertyData getPropertyData(int handle) throws UnknownPropertyException {
190         PropertyData propertyData = propertiesByHandle.get(handle);
191         if (propertyData == null) {
192             throw new UnknownPropertyException(Integer.toString(handle));
193         }
194         return propertyData;
195     }
196 
getPropertyValue(PropertyData propertyData)197     private Object getPropertyValue(PropertyData propertyData) throws WrappedTargetException {
198         Object ret;
199         synchronized (lock) {
200             ret = propertyData.getter.getValue();
201         }
202 
203         // null must not be returned. Either a void any is returned or an any containing
204         // an interface type and a null reference.
205         if (ret == null) {
206             if (propertyData.property.Type.getTypeClass() == TypeClass.INTERFACE) {
207                 ret = new Any(propertyData.property.Type, null);
208             } else {
209                 ret = new Any(new Type(void.class), null);
210             }
211         }
212         return ret;
213     }
214 
215     @Override
getPropertyValue(String propertyName)216     public Object getPropertyValue(String propertyName) throws UnknownPropertyException, WrappedTargetException {
217         PropertyData propertyData = getPropertyData(propertyName);
218         return getPropertyValue(propertyData);
219     }
220 
221     @Override
getFastPropertyValue(int handle)222     public Object getFastPropertyValue(int handle) throws UnknownPropertyException, WrappedTargetException {
223         PropertyData propertyData = getPropertyData(handle);
224         return getPropertyValue(propertyData);
225     }
226 
227     @Override
getPropertyValues(String[] propertyNames)228     public Object[] getPropertyValues(String[] propertyNames) {
229         Object[] values = new Object[propertyNames.length];
230         for (int i = 0; i < propertyNames.length; i++) {
231             Object value = null;
232             try {
233                 value = getPropertyValue(propertyNames[i]);
234             } catch (UnknownPropertyException unknownPropertyException) {
235             } catch (WrappedTargetException wrappedTargetException) {
236             }
237             values[i] = value;
238         }
239         return values;
240     }
241 
242     @Override
removePropertyChangeListener( String propertyName, XPropertyChangeListener listener)243     public void removePropertyChangeListener(
244             String propertyName, XPropertyChangeListener listener) throws UnknownPropertyException, WrappedTargetException {
245         // check existence:
246         getPropertyData(propertyName);
247         boundListeners.removeInterface(propertyName, listener);
248     }
249 
250     @Override
removeVetoableChangeListener( String propertyName, XVetoableChangeListener listener)251     public synchronized void removeVetoableChangeListener(
252             String propertyName, XVetoableChangeListener listener) throws UnknownPropertyException, WrappedTargetException {
253         // check existence:
254         getPropertyData(propertyName);
255         vetoableListeners.removeInterface(propertyName, listener);
256     }
257 
258     @Override
removePropertiesChangeListener(XPropertiesChangeListener listener)259     public void removePropertiesChangeListener(XPropertiesChangeListener listener) {
260         propertiesChangeListeners.remove(listener);
261     }
262 
263     @Override
setPropertyValue(String propertyName, Object value)264     public void setPropertyValue(String propertyName, Object value)
265             throws UnknownPropertyException, PropertyVetoException, IllegalArgumentException, WrappedTargetException {
266         PropertyData propertyData = getPropertyData(propertyName);
267         setPropertyValue(propertyData, value);
268     }
269 
270     @Override
setFastPropertyValue(int handle, Object value)271     public void setFastPropertyValue(int handle, Object value)
272             throws UnknownPropertyException, PropertyVetoException, IllegalArgumentException, WrappedTargetException {
273         PropertyData propertyData = getPropertyData(handle);
274         setPropertyValue(propertyData, value);
275     }
276 
setPropertyValue(PropertyData propertyData, Object value)277     private void setPropertyValue(PropertyData propertyData, Object value)
278             throws UnknownPropertyException, PropertyVetoException, IllegalArgumentException, WrappedTargetException {
279         if ((propertyData.property.Attributes & PropertyAttribute.READONLY) != 0) {
280             throw new PropertyVetoException();
281         }
282         // The value may be null only if MAYBEVOID attribute is set
283         boolean isVoid = false;
284         if (value instanceof Any) {
285             isVoid = ((Any) value).getObject() == null;
286         } else {
287             isVoid = value == null;
288         }
289         if (isVoid && (propertyData.property.Attributes & PropertyAttribute.MAYBEVOID) == 0) {
290             throw new IllegalArgumentException("The property must have a value; the MAYBEVOID attribute is not set!");
291         }
292 
293         // Check if the argument is allowed
294         boolean isValueOk = false;
295         if (value instanceof Any) {
296             isValueOk = checkType(((Any) value).getObject());
297         } else {
298             isValueOk = checkType(value);
299         }
300         if (!isValueOk) {
301             throw new IllegalArgumentException("No valid UNO type");
302         }
303 
304         Object[] futureValue = new Object[] { AnyConverter.toObject(propertyData.property.Type, value) };
305         Object[] currentValue = new Object[] { getPropertyValue(propertyData.property.Name) };
306         Property[] properties = new Property[] { propertyData.property };
307 
308         fire(properties, currentValue, futureValue, false);
309         synchronized (lock) {
310             propertyData.setter.setValue(futureValue[0]);
311         }
312         fire(properties, currentValue, futureValue, true);
313     }
314 
315     @Override
setPropertyValues(String[] propertyNames, Object[] values)316     public void setPropertyValues(String[] propertyNames, Object[] values) throws PropertyVetoException, IllegalArgumentException, WrappedTargetException {
317         for (int i = 0; i < propertyNames.length; i++) {
318             try {
319                 setPropertyValue(propertyNames[i], values[i]);
320             } catch (UnknownPropertyException e) {
321                 continue;
322             }
323         }
324     }
325 
checkType(Object obj)326     private boolean checkType(Object obj) {
327         if (obj == null
328         || obj instanceof Boolean
329         || obj instanceof Character
330         || obj instanceof Number
331         || obj instanceof String
332         || obj instanceof XInterface
333         || obj instanceof Type
334         || obj instanceof com.sun.star.uno.Enum
335         || obj.getClass().isArray())
336             return true;
337         return false;
338     }
339 
340     @Override
firePropertiesChangeEvent(String[] propertyNames, XPropertiesChangeListener listener)341     public void firePropertiesChangeEvent(String[] propertyNames, XPropertiesChangeListener listener) {
342         PropertyChangeEvent[] events = new PropertyChangeEvent[propertyNames.length];
343         int eventCount = 0;
344         for (int i = 0; i < propertyNames.length; i++) {
345             try {
346                 PropertyData propertyData = getPropertyData(propertyNames[i]);
347                 Object value = getPropertyValue(propertyNames[i]);
348                 events[eventCount++] = new PropertyChangeEvent(eventSource, propertyNames[i],
349                         false, propertyData.property.Handle, value, value);
350             } catch (UnknownPropertyException unknownPropertyException) {
351             } catch (WrappedTargetException wrappedTargetException) {
352             }
353         }
354         if (eventCount > 0) {
355             if (events.length != eventCount) {
356                 PropertyChangeEvent[] tmp = new PropertyChangeEvent[eventCount];
357                 System.arraycopy(events, 0, tmp, 0, eventCount);
358                 events = tmp;
359             }
360             listener.propertiesChange(events);
361         }
362     }
363 
fire(Property[] properties, Object[] oldValues, Object[] newValues, boolean hasChanged)364     private void fire(Property[] properties, Object[] oldValues, Object[] newValues, boolean hasChanged) throws PropertyVetoException {
365         PropertyChangeEvent[] events = new PropertyChangeEvent[properties.length];
366         int eventCount = 0;
367         for (int i = 0; i < properties.length; i++) {
368             if ((!hasChanged && (properties[i].Attributes & PropertyAttribute.CONSTRAINED) != 0) ||
369                     (hasChanged && (properties[i].Attributes & PropertyAttribute.BOUND) != 0)) {
370                 events[eventCount++] = new PropertyChangeEvent(
371                         eventSource, properties[i].Name, false, properties[i].Handle, oldValues[i], newValues[i]);
372             }
373         }
374         for (int i = 0; i < eventCount; i++) {
375             fireListeners(hasChanged, events[i].PropertyName, events[i]);
376             fireListeners(hasChanged, "", events[i]);
377         }
378         if (hasChanged && eventCount > 0) {
379             if (eventCount != events.length) {
380                 PropertyChangeEvent[] tmp = new PropertyChangeEvent[eventCount];
381                 System.arraycopy(events, 0, tmp, 0, eventCount);
382                 events = tmp;
383             }
384             for (Iterator<?> it = propertiesChangeListeners.iterator(); it.hasNext();) {
385                 XPropertiesChangeListener listener = (XPropertiesChangeListener) it.next();
386                 listener.propertiesChange(events);
387             }
388         }
389     }
390 
fireListeners(boolean hasChanged, String key, PropertyChangeEvent event)391     private void fireListeners(boolean hasChanged, String key, PropertyChangeEvent event) throws PropertyVetoException {
392         InterfaceContainer listeners;
393         if (hasChanged) {
394             listeners = boundListeners.getContainer(key);
395         } else {
396             listeners = vetoableListeners.getContainer(key);
397         }
398         if (listeners != null) {
399             Iterator<?> it = listeners.iterator();
400             while (it.hasNext()) {
401                 Object listener = it.next();
402                 if (hasChanged) {
403                     ((XPropertyChangeListener)listener).propertyChange(event);
404                 } else {
405                     ((XVetoableChangeListener)listener).vetoableChange(event);
406                 }
407             }
408         }
409     }
410 }
411