1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 package com.sun.star.lib.uno.helper;
29 
30 import com.sun.star.beans.Property;
31 import com.sun.star.beans.PropertyAttribute;
32 import com.sun.star.beans.PropertyChangeEvent;
33 import com.sun.star.beans.PropertyState;
34 import com.sun.star.beans.PropertyValue;
35 import com.sun.star.beans.PropertyVetoException;
36 import com.sun.star.beans.UnknownPropertyException;
37 import com.sun.star.beans.XPropertyChangeListener;
38 import com.sun.star.beans.XPropertySetInfo;
39 import com.sun.star.beans.XVetoableChangeListener;
40 import com.sun.star.container.NoSuchElementException;
41 import com.sun.star.container.XHierarchicalNameAccess;
42 import com.sun.star.lang.DisposedException;
43 import com.sun.star.lang.EventObject;
44 import com.sun.star.lang.WrappedTargetException;
45 import com.sun.star.lang.WrappedTargetRuntimeException;
46 import com.sun.star.lang.XComponent;
47 import com.sun.star.reflection.XCompoundTypeDescription;
48 import com.sun.star.reflection.XIdlClass;
49 import com.sun.star.reflection.XIdlField2;
50 import com.sun.star.reflection.XIdlReflection;
51 import com.sun.star.reflection.XIndirectTypeDescription;
52 import com.sun.star.reflection.XInterfaceAttributeTypeDescription2;
53 import com.sun.star.reflection.XInterfaceMemberTypeDescription;
54 import com.sun.star.reflection.XInterfaceTypeDescription2;
55 import com.sun.star.reflection.XStructTypeDescription;
56 import com.sun.star.reflection.XTypeDescription;
57 import com.sun.star.uno.Any;
58 import com.sun.star.uno.AnyConverter;
59 import com.sun.star.uno.DeploymentException;
60 import com.sun.star.uno.Type;
61 import com.sun.star.uno.TypeClass;
62 import com.sun.star.uno.UnoRuntime;
63 import com.sun.star.uno.XComponentContext;
64 import com.sun.star.uno.XInterface;
65 import java.util.ArrayList;
66 import java.util.HashMap;
67 import java.util.HashSet;
68 import java.util.Iterator;
69 import java.util.Map;
70 import java.util.Vector;
71 
72 /**
73    A helper mixin to implement certain UNO interfaces related to property set
74    handling on top of the attributes of a given UNO interface type.
75 
76    <p>A client will mix in this class by keeping a reference to an instance of
77    this class, and forwarding all methods of (a subset of the interfaces)
78    <code>com.sun.star.beans.XPropertySet</code>,
79    <code>com.sun.star.beans.XFastPropertySet</code>, and
80    <code>com.sun.star.beans.XPropertyAccess</code> to it.</p>
81 
82    <p>Client code should not use the monitors associated with instances of this
83    class, as they are used for internal purposes.</p>
84 
85    @since UDK 3.2
86 */
87 public final class PropertySetMixin {
88     /**
89        The constructor.
90 
91        @param context the component context used by this instance; must not be
92        null, and must supply the service
93        <code>com.sun.star.reflection.CoreReflection</code> and the singleton
94        <code>com.sun.star.reflection.theTypeDescriptionManager</code>
95 
96        @param object the client UNO object into which this instance is mixed in;
97        must not be null, and must support the given <code>type</code>
98 
99        @param type the UNO interface type whose attributes are mapped to
100        properties; must not be null, and must represent a UNO interface type
101 
102        @param absentOptional a list of optional properties that are not present,
103        and should thus not be visible via
104        <code>com.sun.star.beans.XPropertySet.getPropertySetInfo</code>,
105        <code>com.sun.star.beans.XPropertySet.addPropertyChangeListener</code>,
106        <code>com.sun.star.beans.XPropertySet.removePropertyChangeListener<!--
107        --></code>,
108        <code>com.sun.star.beans.XPropertySet.addVetoableChangeListener</code>,
109        and <code>com.sun.star.beans.XPropertySet.<!--
110        -->removeVetoableChangeListener</code>; null is treated the same as an
111        empty list; if non-null, the given array must not be modified after it is
112        passed to this constructor.  For consistency reasons, the given
113        <code>absentOptional</code> should only contain the names of attributes
114        that represent optional properties that are not present (that is, the
115        attribute getters and setters always throw a
116        <code>com.sun.star.beans.UnknownPropertyException</code>), and should
117        contain each such name only once.  If an optional property is not present
118        (that is, the corresponding attribute getter and setter always throw a
119        <code>com.sun.star.beans.UnknownPropertyException</code>) but is not
120        contained in the given <code>absentOptional</code>, then it will be
121        visible via
122        <code>com.sun.star.beans.XPropertySet.getPropertySetInfo</code> as a
123        <code>com.sun.star.beans.Property</code> with a set
124        <code>com.sun.star.beans.PropertyAttribute.OPTIONAL</code>.  If the given
125        <code>object</code> does not implement
126        <code>com.sun.star.beans.XPropertySet</code>, then the given
127        <code>absentOptional</code> is effectively ignored and can be null or
128        empty.
129     */
130     public PropertySetMixin(
131         XComponentContext context, XInterface object, Type type,
132         String[] absentOptional)
133     {
134         // assert context != null && object != null && type != null
135         //     && type.getTypeClass() == TypeClass.INTERFACE;
136         this.context = context;
137         this.object = object;
138         this.type = type;
139         this.absentOptional = absentOptional;
140         idlClass = getReflection(type.getTypeName());
141         XTypeDescription ifc;
142         try {
143             ifc = UnoRuntime.queryInterface(
144                 XTypeDescription.class,
145                 (UnoRuntime.queryInterface(
146                     XHierarchicalNameAccess.class,
147                     context.getValueByName(
148                         "/singletons/com.sun.star.reflection."
149                         + "theTypeDescriptionManager")).
150                  getByHierarchicalName(type.getTypeName())));
151         } catch (NoSuchElementException e) {
152             throw new RuntimeException(
153                 "unexpected com.sun.star.container.NoSuchElementException: "
154                 + e.getMessage());
155         }
156         HashMap map = new HashMap();
157         ArrayList handleNames = new ArrayList();
158         initProperties(ifc, map, handleNames, new HashSet());
159         properties = map;
160         handleMap = (String[]) handleNames.toArray(
161             new String[handleNames.size()]);
162     }
163 
164     /**
165        A method used by clients when implementing UNO interface type attribute
166        setter functions.
167 
168        <p>First, this method checks whether this instance has already been
169        disposed (see {@link #dispose}), and throws a
170        <code>com.sun.star.beans.DisposedException</code> if applicable.  For a
171        constrained attribute (whose setter can explicitly raise
172        <code>com.sun.star.beans.PropertyVetoException</code>), this method
173        notifies any <code>com.sun.star.beans.XVetoableChangeListener</code>s.
174        For a bound attribute, this method modifies the passed-in
175        <code>bound</code> so that it can afterwards be used to notify any
176        <code>com.sun.star.beans.XPropertyChangeListener</code>s.  This method
177        should be called before storing the new attribute value, and
178        <code>bound.notifyListeners()</code> should be called exactly once after
179        storing the new attribute value (in case the attribute is bound;
180        otherwise, calling <code>bound.notifyListeners()</code> is ignored).
181        Furthermore, <code>bound.notifyListeners()</code> and this method have to
182        be called from the same thread.</p>
183 
184        @param propertyName the name of the property (which is the same as the
185        name of the attribute that is going to be set)
186 
187        @param oldValue the property value corresponding to the old attribute
188        value.  This is only used as
189        <code>com.sun.star.beans.PropertyChangeEvent.OldValue</code>, which is
190        rather useless, anyway (see &ldquo;Using the Observer Pattern&rdquo; in
191        <a href="http://tools.openoffice.org/CodingGuidelines.sxw">
192        <cite>OpenOffice.org Coding Guidelines</cite></a>).  If the attribute
193        that is going to be set is neither bound nor constrained, or if
194        <code>com.sun.star.beans.PropertyChangeEvent.OldValue</code> should not
195        be set, {@link Any#VOID} can be used instead.
196 
197        @param newValue the property value corresponding to the new
198        attribute value.  This is only used as
199        <code>com.sun.star.beans.PropertyChangeEvent.NewValue</code>, which is
200        rather useless, anyway (see &ldquo;Using the Observer Pattern&rdquo: in
201        <a href="http://tools.openoffice.org/CodingGuidelines.sxw">
202        <cite>OpenOffice.org Coding Guidelines</cite></a>), <em>unless</em> the
203        attribute that is going to be set is constrained.  If the attribute
204        that is going to be set is neither bound nor constrained, or if it is
205        only bound but
206        <code>com.sun.star.beans.PropertyChangeEvent.NewValue</code> should not
207        be set, {@link Any#VOID} can be used instead.
208 
209        @param bound a reference to a fresh {@link BoundListeners} instance
210        (which has not been passed to this method before, and on which
211        {@link BoundListeners#notifyListeners} has not yet been called); may only
212        be null if the attribute that is going to be set is not bound
213     */
214     public void prepareSet(
215         String propertyName, Object oldValue, Object newValue,
216         BoundListeners bound)
217         throws PropertyVetoException
218     {
219         // assert properties.get(propertyName) != null;
220         Property p = ((PropertyData) properties.get(propertyName)).property;
221         Vector specificVeto = null;
222         Vector unspecificVeto = null;
223         synchronized (this) {
224             if (disposed) {
225                 throw new DisposedException("disposed", object);
226             }
227             if ((p.Attributes & PropertyAttribute.CONSTRAINED) != 0) {
228                 Object o = vetoListeners.get(propertyName);
229                 if (o != null) {
230                     specificVeto = (Vector) ((Vector) o).clone();
231                 }
232                 o = vetoListeners.get("");
233                 if (o != null) {
234                     unspecificVeto = (Vector) ((Vector) o).clone();
235                 }
236             }
237             if ((p.Attributes & PropertyAttribute.BOUND) != 0) {
238                 // assert bound != null;
239                 Object o = boundListeners.get(propertyName);
240                 if (o != null) {
241                     bound.specificListeners = (Vector) ((Vector) o).clone();
242                 }
243                 o = boundListeners.get("");
244                 if (o != null) {
245                     bound.unspecificListeners = (Vector) ((Vector) o).clone();
246                 }
247             }
248         }
249         if ((p.Attributes & PropertyAttribute.CONSTRAINED) != 0) {
250             PropertyChangeEvent event = new PropertyChangeEvent(
251                 object, propertyName, false, p.Handle, oldValue, newValue);
252             if (specificVeto != null) {
253                 for (Iterator i = specificVeto.iterator(); i.hasNext();) {
254                     try {
255                         ((XVetoableChangeListener) i.next()).vetoableChange(
256                             event);
257                     } catch (DisposedException e) {}
258                 }
259             }
260             if (unspecificVeto != null) {
261                 for (Iterator i = unspecificVeto.iterator(); i.hasNext();) {
262                     try {
263                         ((XVetoableChangeListener) i.next()).vetoableChange(
264                             event);
265                     } catch (DisposedException e) {}
266                 }
267             }
268         }
269         if ((p.Attributes & PropertyAttribute.BOUND) != 0) {
270             // assert bound != null;
271             bound.event = new PropertyChangeEvent(
272                 object, propertyName, false, p.Handle, oldValue, newValue);
273         }
274     }
275 
276     /**
277        A simplified version of {@link #prepareSet(String, Object, Object,
278        PropertySetMixin.BoundListeners)}.
279 
280        <p>This method is useful for attributes that are not constrained.</p>
281 
282        @param propertyName the name of the property (which is the same as the
283        name of the attribute that is going to be set)
284 
285        @param bound a reference to a fresh {@link BoundListeners} instance
286        (which has not been passed to this method before, and on which
287        {@link BoundListeners#notifyListeners} has not yet been called); may only
288        be null if the attribute that is going to be set is not bound
289     */
290     public void prepareSet(String propertyName, BoundListeners bound) {
291         try {
292             prepareSet(propertyName, Any.VOID, Any.VOID, bound);
293         } catch (PropertyVetoException e) {
294             throw new RuntimeException("unexpected " + e);
295         }
296     }
297 
298     /**
299        Marks this instance as being disposed.
300 
301        <p>See <code>com.sun.star.lang.XComponent</code> for the general concept
302        of disposing UNO objects.  On the first call to this method, all
303        registered listeners
304        (<code>com.sun.star.beans.XPropertyChangeListener</code>s and
305        <code>com.sun.star.beans.XVetoableChangeListener</code>s) are notified of
306        the disposing source.  Any subsequent calls to this method are
307        ignored.</p>
308      */
309     public void dispose() {
310         HashMap bound;
311         HashMap veto;
312         synchronized (this) {
313             bound = boundListeners;
314             boundListeners = null;
315             veto = vetoListeners;
316             vetoListeners = null;
317             disposed = true;
318         }
319         EventObject event = new EventObject(object);
320         if (bound != null) {
321             for (Iterator i = bound.values().iterator(); i.hasNext();) {
322                 for (Iterator j = ((Vector) i.next()).iterator(); j.hasNext();)
323                 {
324                     ((XPropertyChangeListener) j.next()).disposing(event);
325                 }
326             }
327         }
328         if (veto != null) {
329             for (Iterator i = veto.values().iterator(); i.hasNext();) {
330                 for (Iterator j = ((Vector) i.next()).iterator(); j.hasNext();)
331                 {
332                     ((XVetoableChangeListener) j.next()).disposing(event);
333                 }
334             }
335         }
336     }
337 
338     /**
339        Implements
340        <code>com.sun.star.beans.XPropertySet.getPropertySetInfo</code>.
341     */
342     public XPropertySetInfo getPropertySetInfo() {
343         return new Info(properties);
344     }
345 
346     /**
347        Implements <code>com.sun.star.beans.XPropertySet.setPropertyValue</code>.
348     */
349     public void setPropertyValue(String propertyName, Object value)
350         throws UnknownPropertyException, PropertyVetoException,
351         com.sun.star.lang.IllegalArgumentException, WrappedTargetException
352     {
353         setProperty(propertyName, value, false, false, (short) 1);
354     }
355 
356     /**
357        Implements <code>com.sun.star.beans.XPropertySet.getPropertyValue</code>.
358     */
359     public Object getPropertyValue(String propertyName)
360         throws UnknownPropertyException, WrappedTargetException
361     {
362         return getProperty(propertyName, null);
363     }
364 
365     /**
366        Implements
367        <code>com.sun.star.beans.XPropertySet.addPropertyChangeListener</code>.
368 
369        <p>If a listener is added more than once, it will receive all relevant
370        notifications multiple times.</p>
371     */
372     public void addPropertyChangeListener(
373         String propertyName, XPropertyChangeListener listener)
374         throws UnknownPropertyException, WrappedTargetException
375     {
376         // assert listener != null;
377         checkUnknown(propertyName);
378         boolean disp;
379         synchronized (this) {
380             disp = disposed;
381             if (!disp) {
382                 Vector v = (Vector) boundListeners.get(propertyName);
383                 if (v == null) {
384                     v = new Vector();
385                     boundListeners.put(propertyName, v);
386                 }
387                 v.add(listener);
388             }
389         }
390         if (disp) {
391             listener.disposing(new EventObject(object));
392         }
393     }
394 
395     /**
396        Implements <code>
397        com.sun.star.beans.XPropertySet.removePropertyChangeListener</code>.
398     */
399     public void removePropertyChangeListener(
400         String propertyName, XPropertyChangeListener listener)
401         throws UnknownPropertyException, WrappedTargetException
402     {
403         // assert listener != null;
404         checkUnknown(propertyName);
405         synchronized (this) {
406             if (boundListeners != null) {
407                 Vector v = (Vector) boundListeners.get(propertyName);
408                 if (v != null) {
409                     v.remove(listener);
410                 }
411             }
412         }
413     }
414 
415     /**
416        Implements
417        <code>com.sun.star.beans.XPropertySet.addVetoableChangeListener</code>.
418 
419        <p>If a listener is added more than once, it will receive all relevant
420        notifications multiple times.</p>
421     */
422     public void addVetoableChangeListener(
423         String propertyName, XVetoableChangeListener listener)
424         throws UnknownPropertyException, WrappedTargetException
425     {
426         // assert listener != null;
427         checkUnknown(propertyName);
428         boolean disp;
429         synchronized (this) {
430             disp = disposed;
431             if (!disp) {
432                 Vector v = (Vector) vetoListeners.get(propertyName);
433                 if (v == null) {
434                     v = new Vector();
435                     vetoListeners.put(propertyName, v);
436                 }
437                 v.add(listener);
438             }
439         }
440         if (disp) {
441             listener.disposing(new EventObject(object));
442         }
443     }
444 
445     /**
446        Implements <code>
447        com.sun.star.beans.XPropertySet.removeVetoableChangeListener</code>.
448     */
449     public void removeVetoableChangeListener(
450         String propertyName, XVetoableChangeListener listener)
451         throws UnknownPropertyException, WrappedTargetException
452     {
453         // assert listener != null;
454         checkUnknown(propertyName);
455         synchronized (this) {
456             if (vetoListeners != null) {
457                 Vector v = (Vector) vetoListeners.get(propertyName);
458                 if (v != null) {
459                     v.remove(listener);
460                 }
461             }
462         }
463     }
464 
465     /**
466        Implements
467        <code>com.sun.star.beans.XFastPropertySet.setFastPropertyValue</code>.
468     */
469     public void setFastPropertyValue(int handle, Object value)
470         throws UnknownPropertyException, PropertyVetoException,
471         com.sun.star.lang.IllegalArgumentException, WrappedTargetException
472     {
473         setProperty(translateHandle(handle), value, false, false, (short) 1);
474     }
475 
476     /**
477        Implements
478        <code>com.sun.star.beans.XFastPropertySet.getFastPropertyValue</code>.
479     */
480     public Object getFastPropertyValue(int handle)
481         throws UnknownPropertyException, WrappedTargetException
482     {
483         return getProperty(translateHandle(handle), null);
484     }
485 
486     /**
487        Implements
488        <code>com.sun.star.beans.XPropertyAccess.getPropertyValues</code>.
489     */
490     public PropertyValue[] getPropertyValues() {
491         PropertyValue[] s = new PropertyValue[handleMap.length];
492         int n = 0;
493         for (int i = 0; i < handleMap.length; ++i) {
494             PropertyState[] state = new PropertyState[1];
495             Object value;
496             try {
497                 value = getProperty(handleMap[i], state);
498             } catch (UnknownPropertyException e) {
499                 continue;
500             } catch (WrappedTargetException e) {
501                 throw new WrappedTargetRuntimeException(
502                     e.getMessage(), object, e.TargetException);
503             }
504             s[n++] = new PropertyValue(handleMap[i], i, value, state[0]);
505         }
506         if (n < handleMap.length) {
507             PropertyValue[] s2 = new PropertyValue[n];
508             System.arraycopy(s, 0, s2, 0, n);
509             s = s2;
510         }
511         return s;
512     }
513 
514     /**
515        Implements
516        <code>com.sun.star.beans.XPropertyAccess.setPropertyValues</code>.
517     */
518     public void setPropertyValues(PropertyValue[] props)
519         throws UnknownPropertyException, PropertyVetoException,
520         com.sun.star.lang.IllegalArgumentException, WrappedTargetException
521     {
522         for (int i = 0; i < props.length; ++i) {
523             if (props[i].Handle != -1
524                 && !props[i].Name.equals(translateHandle(props[i].Handle)))
525             {
526                 throw new UnknownPropertyException(
527                     ("name " + props[i].Name + " does not match handle "
528                      + props[i].Handle),
529                     object);
530             }
531             setProperty(
532                 props[i].Name, props[i].Value,
533                 props[i].State == PropertyState.AMBIGUOUS_VALUE,
534                 props[i].State == PropertyState.DEFAULT_VALUE, (short) 0);
535         }
536     }
537 
538     /**
539        A class used by clients of {@link PropertySetMixin} when implementing UNO
540        interface type attribute setter functions.
541 
542        @see #prepareSet(String, Object, Object, PropertySetMixin.BoundListeners)
543     */
544     public static final class BoundListeners {
545         /**
546            The constructor.
547         */
548         public BoundListeners() {}
549 
550         /**
551            Notifies any
552            <code>com.sun.star.beans.XPropertyChangeListener</code>s.
553 
554            @see #prepareSet(String, Object, Object,
555            PropertySetMixin.BoundListeners)
556         */
557         public void notifyListeners() {
558             if (specificListeners != null) {
559                 for (Iterator i = specificListeners.iterator(); i.hasNext();) {
560                     try {
561                         ((XPropertyChangeListener) i.next()).propertyChange(
562                             event);
563                     } catch (DisposedException e) {}
564                 }
565             }
566             if (unspecificListeners != null) {
567                 for (Iterator i = unspecificListeners.iterator(); i.hasNext();)
568                 {
569                     try {
570                         ((XPropertyChangeListener) i.next()).propertyChange(
571                             event);
572                     } catch (DisposedException e) {}
573                 }
574             }
575         }
576 
577         private Vector specificListeners = null;
578         private Vector unspecificListeners = null;
579         private PropertyChangeEvent event = null;
580     }
581 
582     private XIdlClass getReflection(String typeName) {
583         XIdlReflection refl;
584         try {
585             refl = UnoRuntime.queryInterface(
586                 XIdlReflection.class,
587                 context.getServiceManager().createInstanceWithContext(
588                     "com.sun.star.reflection.CoreReflection", context));
589         } catch (com.sun.star.uno.Exception e) {
590             throw new DeploymentException(
591                 ("component context fails to supply service"
592                  + " com.sun.star.reflection.CoreReflection: "
593                  + e.getMessage()),
594                 context);
595         }
596         try {
597             return refl.forName(typeName);
598         } finally {
599             XComponent comp = UnoRuntime.queryInterface(XComponent.class, refl);
600             if (comp != null) {
601                 comp.dispose();
602             }
603         }
604     }
605 
606     private void initProperties(
607         XTypeDescription type, HashMap map, ArrayList handleNames, HashSet seen)
608     {
609         XInterfaceTypeDescription2 ifc = UnoRuntime.queryInterface(
610             XInterfaceTypeDescription2.class, resolveTypedefs(type));
611         if (seen.add(ifc.getName())) {
612             XTypeDescription[] bases = ifc.getBaseTypes();
613             for (int i = 0; i < bases.length; ++i) {
614                 initProperties(bases[i], map, handleNames, seen);
615             }
616             XInterfaceMemberTypeDescription[] members = ifc.getMembers();
617             for (int i = 0; i < members.length; ++i) {
618                 if (members[i].getTypeClass() == TypeClass.INTERFACE_ATTRIBUTE)
619                 {
620                     XInterfaceAttributeTypeDescription2 attr =
621                         UnoRuntime.queryInterface(
622                             XInterfaceAttributeTypeDescription2.class,
623                             members[i]);
624                     short attrAttribs = 0;
625                     if (attr.isBound()) {
626                         attrAttribs |= PropertyAttribute.BOUND;
627                     }
628                     boolean setUnknown = false;
629                     if (attr.isReadOnly()) {
630                         attrAttribs |= PropertyAttribute.READONLY;
631                         setUnknown = true;
632                     }
633                     XCompoundTypeDescription[] excs = attr.getGetExceptions();
634                     boolean getUnknown = false;
635                     //XXX  Special interpretation of getter/setter exceptions
636                     // only works if the specified exceptions are of the exact
637                     // type, not of a supertype:
638                     for (int j = 0; j < excs.length; ++j) {
639                         if (excs[j].getName().equals(
640                                 "com.sun.star.beans.UnknownPropertyException"))
641                         {
642                             getUnknown = true;
643                             break;
644                         }
645                     }
646                     excs = attr.getSetExceptions();
647                     for (int j = 0; j < excs.length; ++j) {
648                         if (excs[j].getName().equals(
649                                 "com.sun.star.beans.UnknownPropertyException"))
650                         {
651                             setUnknown = true;
652                         } else if (excs[j].getName().equals(
653                                        "com.sun.star.beans."
654                                        + "PropertyVetoException"))
655                         {
656                             attrAttribs |= PropertyAttribute.CONSTRAINED;
657                         }
658                     }
659                     if (getUnknown && setUnknown) {
660                         attrAttribs |= PropertyAttribute.OPTIONAL;
661                     }
662                     XTypeDescription t = attr.getType();
663                     for (;;) {
664                         t = resolveTypedefs(t);
665                         short n;
666                         if (t.getName().startsWith(
667                                 "com.sun.star.beans.Ambiguous<"))
668                         {
669                             n = PropertyAttribute.MAYBEAMBIGUOUS;
670                         } else if (t.getName().startsWith(
671                                        "com.sun.star.beans.Defaulted<"))
672                         {
673                             n = PropertyAttribute.MAYBEDEFAULT;
674                         } else if (t.getName().startsWith(
675                                        "com.sun.star.beans.Optional<"))
676                         {
677                             n = PropertyAttribute.MAYBEVOID;
678                         } else {
679                             break;
680                         }
681                         attrAttribs |= n;
682                         t = (UnoRuntime.queryInterface(
683                                  XStructTypeDescription.class, t)).
684                             getTypeArguments()[0];
685                     }
686                     String name = members[i].getMemberName();
687                     boolean present = true;
688                     if (absentOptional != null) {
689                         for (int j = 0; j < absentOptional.length; ++j) {
690                             if (name.equals(absentOptional[j])) {
691                                 present = false;
692                                 break;
693                             }
694                         }
695                     }
696                     if (map.put(
697                             name,
698                             new PropertyData(
699                                 new Property(
700                                     name, handleNames.size(),
701                                     new Type(t.getName(), t.getTypeClass()),
702                                     attrAttribs),
703                                 present))
704                         != null)
705                     {
706                         throw new RuntimeException(
707                             "inconsistent UNO type registry");
708                     }
709                     handleNames.add(name);
710                 }
711             }
712         }
713     }
714 
715     private String translateHandle(int handle) throws UnknownPropertyException {
716         if (handle < 0 || handle >= handleMap.length) {
717             throw new UnknownPropertyException("bad handle " + handle, object);
718         }
719         return handleMap[handle];
720     }
721 
722     private void setProperty(
723         String name, Object value, boolean isAmbiguous, boolean isDefaulted,
724         short illegalArgumentPosition)
725         throws UnknownPropertyException, PropertyVetoException,
726         com.sun.star.lang.IllegalArgumentException, WrappedTargetException
727     {
728         PropertyData p = (PropertyData) properties.get(name);
729         if (p == null) {
730             throw new UnknownPropertyException(name, object);
731         }
732         if ((isAmbiguous
733              && (p.property.Attributes & PropertyAttribute.MAYBEAMBIGUOUS) == 0)
734             || (isDefaulted
735                 && ((p.property.Attributes & PropertyAttribute.MAYBEDEFAULT)
736                     == 0)))
737         {
738             throw new com.sun.star.lang.IllegalArgumentException(
739                 ("flagging as ambiguous/defaulted non-ambiguous/defaulted"
740                  + " property " + name),
741                 object, illegalArgumentPosition);
742 
743         }
744         XIdlField2 f = UnoRuntime.queryInterface(
745             XIdlField2.class, idlClass.getField(name));
746         Object[] o = new Object[] {
747                 new Any(type, UnoRuntime.queryInterface(type, object)) };
748         Object v = wrapValue(
749             value,
750             UnoRuntime.queryInterface(
751                 XIdlField2.class, idlClass.getField(name)).getType(),
752             (p.property.Attributes & PropertyAttribute.MAYBEAMBIGUOUS) != 0,
753             isAmbiguous,
754             (p.property.Attributes & PropertyAttribute.MAYBEDEFAULT) != 0,
755             isDefaulted,
756             (p.property.Attributes & PropertyAttribute.MAYBEVOID) != 0);
757         try {
758             f.set(o, v);
759         } catch (com.sun.star.lang.IllegalArgumentException e) {
760             if (e.ArgumentPosition == 1) {
761                 throw new com.sun.star.lang.IllegalArgumentException(
762                     e.getMessage(), object, illegalArgumentPosition);
763             } else {
764                 throw new RuntimeException(
765                     "unexpected com.sun.star.lang.IllegalArgumentException: "
766                     + e.getMessage());
767             }
768         } catch (com.sun.star.lang.IllegalAccessException e) {
769             //TODO  Clarify whether PropertyVetoException is the correct
770             // exception to throw when trying to set a read-only property:
771             throw new PropertyVetoException(
772                 "cannot set read-only property " + name, object);
773         } catch (WrappedTargetRuntimeException e) {
774             //FIXME  A WrappedTargetRuntimeException from XIdlField2.get is not
775             // guaranteed to originate directly within XIdlField2.get (and thus
776             // have the expected semantics); it might also be passed through
777             // from lower layers.
778             if (new Type(UnknownPropertyException.class).isSupertypeOf(
779                     AnyConverter.getType(e.TargetException))
780                 && (p.property.Attributes & PropertyAttribute.OPTIONAL) != 0)
781             {
782                 throw new UnknownPropertyException(name, object);
783             } else if (new Type(PropertyVetoException.class).isSupertypeOf(
784                            AnyConverter.getType(e.TargetException))
785                        && ((p.property.Attributes
786                             & PropertyAttribute.CONSTRAINED)
787                            != 0))
788             {
789                 throw new PropertyVetoException(name, object);
790             } else {
791                 throw new WrappedTargetException(
792                     e.getMessage(), object, e.TargetException);
793             }
794         }
795     }
796 
797     Object getProperty(String name, PropertyState[] state)
798         throws UnknownPropertyException, WrappedTargetException
799     {
800         PropertyData p = (PropertyData) properties.get(name);
801         if (p == null) {
802             throw new UnknownPropertyException(name, object);
803         }
804         XIdlField2 field = UnoRuntime.queryInterface(
805             XIdlField2.class, idlClass.getField(name));
806         Object value;
807         try {
808             value = field.get(
809                 new Any(type, UnoRuntime.queryInterface(type, object)));
810         } catch (com.sun.star.lang.IllegalArgumentException e) {
811             throw new RuntimeException(
812                 "unexpected com.sun.star.lang.IllegalArgumentException: "
813                 + e.getMessage());
814         } catch (WrappedTargetRuntimeException e) {
815             //FIXME  A WrappedTargetRuntimeException from XIdlField2.get is not
816             // guaranteed to originate directly within XIdlField2.get (and thus
817             // have the expected semantics); it might also be passed through
818             // from lower layers.
819             if (new Type(UnknownPropertyException.class).isSupertypeOf(
820                     AnyConverter.getType(e.TargetException))
821                 && (p.property.Attributes & PropertyAttribute.OPTIONAL) != 0)
822             {
823                 throw new UnknownPropertyException(name, object);
824             } else {
825                 throw new WrappedTargetException(
826                     e.getMessage(), object, e.TargetException);
827             }
828         }
829         boolean undoAmbiguous
830             = (p.property.Attributes & PropertyAttribute.MAYBEAMBIGUOUS) != 0;
831         boolean undoDefaulted
832             = (p.property.Attributes & PropertyAttribute.MAYBEDEFAULT) != 0;
833         boolean undoOptional
834             = (p.property.Attributes & PropertyAttribute.MAYBEVOID) != 0;
835         boolean isAmbiguous = false;
836         boolean isDefaulted = false;
837         while (undoAmbiguous || undoDefaulted || undoOptional) {
838             String typeName = AnyConverter.getType(value).getTypeName();
839             if (undoAmbiguous
840                 && typeName.startsWith("com.sun.star.beans.Ambiguous<"))
841             {
842                 XIdlClass ambiguous = getReflection(typeName);
843                 try {
844                     isAmbiguous = AnyConverter.toBoolean(
845                         UnoRuntime.queryInterface(
846                             XIdlField2.class,
847                             ambiguous.getField("IsAmbiguous")).get(value));
848                     value = UnoRuntime.queryInterface(
849                         XIdlField2.class,
850                         ambiguous.getField("Value")).get(value);
851                 } catch (com.sun.star.lang.IllegalArgumentException e) {
852                     throw new RuntimeException(
853                         "unexpected"
854                         + " com.sun.star.lang.IllegalArgumentException: "
855                         + e.getMessage());
856                 }
857                 undoAmbiguous = false;
858             } else if (undoDefaulted
859                        && typeName.startsWith("com.sun.star.beans.Defaulted<"))
860             {
861                 XIdlClass defaulted = getReflection(typeName);
862                 try {
863                     isDefaulted = AnyConverter.toBoolean(
864                         UnoRuntime.queryInterface(
865                             XIdlField2.class,
866                             defaulted.getField("IsDefaulted")).get(value));
867                     value = UnoRuntime.queryInterface(
868                         XIdlField2.class,
869                         defaulted.getField("Value")).get(value);
870                 } catch (com.sun.star.lang.IllegalArgumentException e) {
871                     throw new RuntimeException(
872                         "unexpected"
873                         + " com.sun.star.lang.IllegalArgumentException: "
874                         + e.getMessage());
875                 }
876                 undoDefaulted = false;
877             } else if (undoOptional
878                        && typeName.startsWith("com.sun.star.beans.Optional<"))
879             {
880                 XIdlClass optional = getReflection(typeName);
881                 try {
882                     boolean present = AnyConverter.toBoolean(
883                         UnoRuntime.queryInterface(
884                             XIdlField2.class,
885                             optional.getField("IsPresent")).get(value));
886                     if (!present) {
887                         value = Any.VOID;
888                         break;
889                     }
890                     value = UnoRuntime.queryInterface(
891                         XIdlField2.class,
892                         optional.getField("Value")).get(value);
893                 } catch (com.sun.star.lang.IllegalArgumentException e) {
894                     throw new RuntimeException(
895                         "unexpected"
896                         + " com.sun.star.lang.IllegalArgumentException: "
897                         + e.getMessage());
898                 }
899                 undoOptional = false;
900             } else {
901                 throw new RuntimeException(
902                     "unexpected type of attribute " + name);
903             }
904         }
905         if (state != null) {
906             //XXX  If isAmbiguous && isDefaulted, arbitrarily choose
907             // AMBIGUOUS_VALUE over DEFAULT_VALUE:
908             state[0] = isAmbiguous
909                 ? PropertyState.AMBIGUOUS_VALUE
910                 : isDefaulted
911                 ? PropertyState.DEFAULT_VALUE : PropertyState.DIRECT_VALUE;
912         }
913         return value;
914     }
915 
916     private Object wrapValue(
917         Object value, XIdlClass type, boolean wrapAmbiguous,
918         boolean isAmbiguous, boolean wrapDefaulted, boolean isDefaulted,
919         boolean wrapOptional)
920     {
921         // assert (wrapAmbiguous || !isAmbiguous)
922         //     && (wrapDefaulted || !isDefaulted);
923         if (wrapAmbiguous
924             && type.getName().startsWith("com.sun.star.beans.Ambiguous<"))
925         {
926             Object[] strct = new Object[1];
927             type.createObject(strct);
928             try {
929                 XIdlField2 field = UnoRuntime.queryInterface(
930                     XIdlField2.class, type.getField("Value"));
931                 field.set(
932                     strct,
933                     wrapValue(
934                         value, field.getType(), false, false, wrapDefaulted,
935                         isDefaulted, wrapOptional));
936                 UnoRuntime.queryInterface(
937                     XIdlField2.class, type.getField("IsAmbiguous")).set(
938                         strct, new Boolean(isAmbiguous));
939             } catch (com.sun.star.lang.IllegalArgumentException e) {
940                 throw new RuntimeException(
941                     "unexpected com.sun.star.lang.IllegalArgumentException: "
942                     + e.getMessage());
943             } catch (com.sun.star.lang.IllegalAccessException e) {
944                 throw new RuntimeException(
945                     "unexpected com.sun.star.lang.IllegalAccessException: "
946                     + e.getMessage());
947             }
948             return strct[0];
949         } else if (wrapDefaulted
950                    && type.getName().startsWith(
951                        "com.sun.star.beans.Defaulted<"))
952         {
953             Object[] strct = new Object[1];
954             type.createObject(strct);
955             try {
956                 XIdlField2 field = UnoRuntime.queryInterface(
957                     XIdlField2.class, type.getField("Value"));
958                 field.set(
959                     strct,
960                     wrapValue(
961                         value, field.getType(), wrapAmbiguous, isAmbiguous,
962                         false, false, wrapOptional));
963                 UnoRuntime.queryInterface(
964                     XIdlField2.class, type.getField("IsDefaulted")).set(
965                         strct, new Boolean(isDefaulted));
966             } catch (com.sun.star.lang.IllegalArgumentException e) {
967                 throw new RuntimeException(
968                     "unexpected com.sun.star.lang.IllegalArgumentException: "
969                     + e.getMessage());
970             } catch (com.sun.star.lang.IllegalAccessException e) {
971                 throw new RuntimeException(
972                     "unexpected com.sun.star.lang.IllegalAccessException: "
973                     + e.getMessage());
974             }
975             return strct[0];
976         } else if (wrapOptional
977                    && type.getName().startsWith("com.sun.star.beans.Optional<"))
978         {
979             Object[] strct = new Object[1];
980             type.createObject(strct);
981             boolean present = !AnyConverter.isVoid(value);
982             try {
983                 UnoRuntime.queryInterface(
984                     XIdlField2.class, type.getField("IsPresent")).set(
985                         strct, new Boolean(present));
986                 if (present) {
987                     XIdlField2 field = UnoRuntime.queryInterface(
988                         XIdlField2.class, type.getField("Value"));
989                     field.set(
990                         strct,
991                         wrapValue(
992                             value, field.getType(), wrapAmbiguous, isAmbiguous,
993                             wrapDefaulted, isDefaulted, false));
994                 }
995             } catch (com.sun.star.lang.IllegalArgumentException e) {
996                 throw new RuntimeException(
997                     "unexpected com.sun.star.lang.IllegalArgumentException: "
998                     + e.getMessage());
999             } catch (com.sun.star.lang.IllegalAccessException e) {
1000                 throw new RuntimeException(
1001                     "unexpected com.sun.star.lang.IllegalAccessException: "
1002                     + e.getMessage());
1003             }
1004             return strct[0];
1005         } else {
1006             if (wrapAmbiguous || wrapDefaulted || wrapOptional) {
1007                 throw new RuntimeException("unexpected type of attribute");
1008             }
1009             return value;
1010         }
1011     }
1012 
1013     private static XTypeDescription resolveTypedefs(XTypeDescription type) {
1014         while (type.getTypeClass() == TypeClass.TYPEDEF) {
1015             type = UnoRuntime.queryInterface(
1016                 XIndirectTypeDescription.class, type).getReferencedType();
1017         }
1018         return type;
1019     }
1020 
1021     private PropertyData get(Object object, String propertyName)
1022         throws UnknownPropertyException
1023     {
1024         PropertyData p = (PropertyData) properties.get(propertyName);
1025         if (p == null || !p.present) {
1026             throw new UnknownPropertyException(propertyName, object);
1027         }
1028         return p;
1029     }
1030 
1031     private void checkUnknown(String propertyName)
1032         throws UnknownPropertyException
1033     {
1034         if (!propertyName.equals("")) {
1035             get(this, propertyName);
1036         }
1037     }
1038 
1039     private static final class PropertyData {
1040         public PropertyData(Property property, boolean present) {
1041             this.property = property;
1042             this.present = present;
1043         }
1044 
1045         public final Property property;
1046         public final boolean present;
1047     }
1048 
1049     private final class Info extends WeakBase implements XPropertySetInfo
1050     {
1051         public Info(Map properties) {
1052             this.properties = properties;
1053         }
1054 
1055         public Property[] getProperties() {
1056             ArrayList al = new ArrayList(properties.size());
1057             for (Iterator i = properties.values().iterator(); i.hasNext();) {
1058                 PropertyData p = (PropertyData) i.next();
1059                 if (p.present) {
1060                     al.add(p.property);
1061                 }
1062             }
1063             return (Property[]) al.toArray(new Property[al.size()]);
1064         }
1065 
1066         public Property getPropertyByName(String name)
1067             throws UnknownPropertyException
1068         {
1069             return get(this, name).property;
1070         }
1071 
1072         public boolean hasPropertyByName(String name) {
1073             PropertyData p = (PropertyData) properties.get(name);
1074             return p != null && p.present;
1075         }
1076 
1077         private final Map properties; // from String to Property
1078     }
1079 
1080     private final XComponentContext context;
1081     private final XInterface object;
1082     private final Type type;
1083     private final String[] absentOptional;
1084     private final XIdlClass idlClass;
1085     private final Map properties; // from String to Property
1086     private final String[] handleMap;
1087 
1088     private HashMap boundListeners = new HashMap();
1089         // from String to Vector of XPropertyChangeListener
1090     private HashMap vetoListeners = new HashMap();
1091         // from String to Vector of XVetoableChangeListener
1092     private boolean disposed = false;
1093 }
1094