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