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 lib;
25 
26 import com.sun.star.beans.Property;
27 import com.sun.star.beans.PropertyAttribute;
28 import com.sun.star.beans.PropertyVetoException;
29 import com.sun.star.beans.XPropertySet;
30 import com.sun.star.beans.XPropertySetInfo;
31 import com.sun.star.beans.UnknownPropertyException;
32 import com.sun.star.lang.XServiceInfo;
33 import com.sun.star.lang.IllegalArgumentException;
34 import com.sun.star.lang.WrappedTargetException;
35 import com.sun.star.uno.UnoRuntime;
36 
37 import java.lang.reflect.Method;
38 
39 import util.ValueChanger;
40 import util.ValueComparer;
41 import util.utils;
42 
43 import com.sun.star.uno.Any;
44 import com.sun.star.uno.AnyConverter;
45 import com.sun.star.uno.Type;
46 
47 /**
48  * MultiPropertyTest extends the functionality of MultiMethodTest to support
49  * services testing. Since, in most cases, service tests has one method testing
50  * most of its properties, the MultiPropertyTest provides unified version of
51  * the method: testProperty().
52  *
53  * <p>The testProperty() is called, when the MultiMethodTest's testing method
54  * is not found in the subclass. So, by defining such methods for properties
55  * the standard testing behavior can be changed.
56  *
57  * <p>The testing behavior also can be changed by overriding compare(),
58  * getNewVAlue() or toString(Object) methods, or by extending PropertyTester
59  * class.
60  *
61  * @see MultiMethodTest
62  * @see #testProperty(String)
63  * @see #testProperty(String, PropertyTester)
64  * @see #getNewValue
65  * @see #compare
66  * @see #toString(Object)
67  */
68 public class MultiPropertyTest extends MultiMethodTest
69 {
70 
71     /**
72      * Contains a XPropertySet interface of the tested object. Is initialized
73      * in MultiMethodTest code.
74      */
75     public XPropertySet oObj;
76     protected boolean optionalService = false;
77 
78     /**
79      * Overrides super.before() to check the service is supported by the object.
80      */
before()81     protected void before()
82     {
83         XServiceInfo xInfo = (XServiceInfo) UnoRuntime.queryInterface(
84                 XServiceInfo.class, oObj);
85 
86         optionalService = entry.isOptional;
87 
88         String theService = getTestedClassName();
89         if (xInfo != null && !xInfo.supportsService(theService))
90         {
91             log.println("Service " + theService + " not available");
92             if (optionalService)
93             {
94                 log.println("This is OK since it is optional");
95             }
96             else
97             {
98                 Status.failed(theService + " is not supported");
99             }
100         }
101     }
102 
103     /**
104      * Overrides MultiMethodTest.invokeTestMethod(). If the test for the
105      * <code>meth</code> is not available (<code>meth</code> == <tt>null</tt>)
106      * calls testProperty method for the method. Otherwise calls
107      * super.invokeTestMethod().
108      *
109      * @see MultiMethodTest#invokeTestMethod
110      */
invokeTestMethod(Method meth, String methName)111     protected void invokeTestMethod(Method meth, String methName)
112     {
113         if (meth != null)
114         {
115             super.invokeTestMethod(meth, methName);
116         }
117         else
118         {
119             testProperty(methName);
120         }
121     }
122 
123     /**
124      * PropertyTester class defines how to test a property and defined
125      * to allow subclasses of MultiPropertyTest to change the testing
126      * behavior more flexible, since the behavior can be customized for
127      * each property separately, by providing subclass of PropertyTester
128      * and passing it to testProperty(String, PropertyTester method).
129      */
130     public class PropertyTester
131     {
132 
133         /**
134          * The method defines the whole process of testing propName
135          * property.
136          *
137          * <p>First, it checks if the property exists(it maybe optional).
138          * Then, a value to set the property with is calculated with
139          * getNewValue method. Normally, the new value is calculated
140          * based on old value, but subclasses can override the behaviour
141          * (for example, if old value is null) and specify their own value.
142          * Then the property is set with that new value and the result(
143          * it maybe an exception too, for example a PropertyVetoException)
144          * is checked with checkResult method.
145          *
146          * @param propName - the property to test.
147          * @result - adds the result of testing propName property to
148          *           MultiMethodTest.tRes.
149          */
testProperty(String propName)150         protected void testProperty(String propName)
151         {
152             XPropertySetInfo info = oObj.getPropertySetInfo();
153 
154             if (info != null)
155             {
156                 final boolean bHasProperty = info.hasPropertyByName(propName);
157                 if (!bHasProperty)
158                 {
159                     if (isOptional(propName) || optionalService)
160                     {
161                         // skipping optional property test
162                         log.println("Property '" + propName + "' is optional and not supported");
163                         tRes.tested(propName, true);
164                         return;
165                     }
166                     else
167                     {
168                         // cannot test the property
169                         log.println("Tested XPropertySet does not contain'" + propName + "' property");
170                         tRes.tested(propName, false);
171                         return;
172                     }
173                 }
174             }
175 
176             try
177             {
178                 Object oldValue = oObj.getPropertyValue(propName);
179 
180                 if( (oldValue==null) || utils.isVoid(oldValue) )
181                 {
182                     // #i111560# method getNewValue() does not work with an empty oldValue
183                     Property prop = info.getPropertyByName(propName);
184                     if( (prop.Attributes & PropertyAttribute.MAYBEVOID) != 0 )
185                     {
186                         // TODO: implement a new test independent from method getNewValue()
187                         log.println("changing initially empty MAYBEVOID properties is not supported by the test framework so far - skip test of property: " + propName);
188                         tRes.tested(propName, true);
189                         return;
190                     }
191                     else
192                     {
193                         log.println( "property '"+propName+"' is not set but is not MAYBEVOID");
194                         tRes.tested(propName, false);
195                         return;
196                     }
197                 }
198 
199                 Object newValue;
200 
201                 // trying to create new value
202                 try
203                 {
204                     newValue = getNewValue(propName, oldValue);
205                 }
206                 catch (java.lang.IllegalArgumentException e)
207                 {
208                     // skipping test since new value is not available
209                     Status.failed("Cannot create new value for '" + propName + " : " + e.getMessage());
210                     return;
211                 }
212 
213                 // for an exception thrown during setting new value
214                 // to pass it to checkResult method
215                 Exception exception = null;
216 
217                 try
218                 {
219                     log.println("try to set:");
220                     log.println("old = " + toString(oldValue));
221                     log.println("new = " + toString(newValue));
222                     oObj.setPropertyValue(propName, newValue);
223                 }
224                 catch (IllegalArgumentException e)
225                 {
226                     exception = e;
227                 }
228                 catch (PropertyVetoException e)
229                 {
230                     exception = e;
231                 }
232                 catch (WrappedTargetException e)
233                 {
234                     exception = e;
235                 }
236                 catch (UnknownPropertyException e)
237                 {
238                     exception = e;
239                 }
240                 catch (RuntimeException e)
241                 {
242                     exception = e;
243                 }
244 
245                 // getting result value
246                 Object resValue = oObj.getPropertyValue(propName);
247 
248                 // checking results
249                 checkResult(propName, oldValue, newValue, resValue, exception);
250             }
251             catch (Exception e)
252             {
253                 log.println("Exception occurred while testing property '" + propName + "'");
254                 e.printStackTrace(log);
255                 tRes.tested(propName, false);
256             }
257         }
258 
259         /**
260          * The method checks result of setting a new value to the
261          * property based o the following arguments:
262          * @param propName - the property to test
263          * @param oldValue - the old value of the property, before changing it.
264          * @param newValue - the new value the property has been set with
265          * @param resValue - the value of the property after having changed it
266          * @param exception - if not null - the exception thrown by
267          *                 XPropertySet.setPropertyValue, else indicates
268          *                 normal method completion.
269          *
270          * <p>If the property is READ_ONLY, than either PropertyVetoException
271          * should be thrown or the value of property should not have changed
272          * (resValue is compared with oldValue with compare method).
273          *
274          * <p>If the property is not READ_ONLY, checks that the new value has
275          * been successfully set (resValue is compared with newValue with
276          * compare method).
277          *
278          * <p>If the exception is not null then (except the case of read-only
279          * property and PropertyVetoException above) it is rethrown to allow
280          * further catching it if needed.
281          *
282          * <p>Subclasses can override to change this behavior.
283          */
checkResult(String propName, Object oldValue, Object newValue, Object resValue, Exception exception)284         protected void checkResult(String propName, Object oldValue,
285                 Object newValue, Object resValue, Exception exception)
286                 throws Exception
287         {
288             XPropertySetInfo info = oObj.getPropertySetInfo();
289             if (info == null)
290             {
291                 log.println("Can't get XPropertySetInfo for property " + propName);
292                 tRes.tested(propName, false);
293                 return;
294             }
295             Property prop = info.getPropertyByName(propName);
296 
297             short attr = prop.Attributes;
298             boolean readOnly = (prop.Attributes & PropertyAttribute.READONLY) != 0;
299             boolean maybeVoid = (prop.Attributes & PropertyAttribute.MAYBEVOID) != 0;
300             // check get-set methods
301             if (maybeVoid)
302             {
303                 log.println("Property " + propName + " is void");
304             }
305             if (readOnly)
306             {
307                 log.println("Property " + propName + " is readOnly");
308             }
309             if (util.utils.isVoid(oldValue) && !maybeVoid)
310             {
311                 log.println(propName + " is void, but it's not MAYBEVOID");
312                 tRes.tested(propName, false);
313             }
314             else if (oldValue == null)
315             {
316                 log.println(propName + " has null value, and therefore can't be changed");
317                 tRes.tested(propName, true);
318             }
319             else if (readOnly)
320             {
321                 // check if exception was thrown
322                 if (exception != null)
323                 {
324                     if (exception instanceof PropertyVetoException)
325                     {
326                         // the change of read only prohibited - OK
327                         log.println("Property is ReadOnly and wasn't changed");
328                         log.println("Property '" + propName + "' OK");
329                         tRes.tested(propName, true);
330                     }
331                     else if (exception instanceof IllegalArgumentException)
332                     {
333                         // the change of read only prohibited - OK
334                         log.println("Property is ReadOnly and wasn't changed");
335                         log.println("Property '" + propName + "' OK");
336                         tRes.tested(propName, true);
337                     }
338                     else if (exception instanceof UnknownPropertyException)
339                     {
340                         // the change of read only prohibited - OK
341                         log.println("Property is ReadOnly and wasn't changed");
342                         log.println("Property '" + propName + "' OK");
343                         tRes.tested(propName, true);
344                     }
345                     else if (exception instanceof RuntimeException)
346                     {
347                         // the change of read only prohibited - OK
348                         log.println("Property is ReadOnly and wasn't changed");
349                         log.println("Property '" + propName + "' OK");
350                         tRes.tested(propName, true);
351                     }
352                     else
353                     {
354                         throw exception;
355                     }
356                 }
357                 else
358                 {
359                     // if no exception - check that value
360                     // has not changed
361                     if (!compare(resValue, oldValue))
362                     {
363                         log.println("Read only property '" + propName + "' has changed");
364                         try
365                         {
366                             if (!util.utils.isVoid(oldValue) && oldValue instanceof Any)
367                             {
368                                 oldValue = AnyConverter.toObject(new Type(((Any) oldValue).getClass()), oldValue);
369                             }
370 //                            log.println("old = " + toString(oldValue));
371 //                            log.println("new = " + toString(newValue));
372                             log.println("result = " + toString(resValue));
373                         }
374                         catch (com.sun.star.lang.IllegalArgumentException iae)
375                         {
376                             log.println("NOTIFY: this property needs further investigations.");
377                             log.println("\t The type seems to be an Any with value of NULL.");
378                             log.println("\t Maybe the property should get it's own test method.");
379                         }
380 
381                         tRes.tested(propName, false);
382                     }
383                     else
384                     {
385                         log.println("Read only property '" + propName + "' hasn't changed");
386                         log.println("Property '" + propName + "' OK");
387                         tRes.tested(propName, true);
388                     }
389                 }
390             }
391             else
392             {
393                 if (exception == null)
394                 {
395                     // if no exception thrown
396                     // check that the new value is set
397                     if ((!compare(resValue, newValue)) || (compare(resValue, oldValue)))
398                     {
399                         log.println("Value for '" + propName + "' hasn't changed as expected");
400                         try
401                         {
402                             if (!util.utils.isVoid(oldValue) && oldValue instanceof Any)
403                             {
404                                 oldValue = AnyConverter.toObject(new Type(((Any) oldValue).getClass()), oldValue);
405                             }
406 //                            log.println("old = " + toString(oldValue));
407 //                            log.println("new = " + toString(newValue));
408                             log.println("result = " + toString(resValue));
409                         }
410                         catch (com.sun.star.lang.IllegalArgumentException iae)
411                         {
412                             log.println("NOTIFY: this property needs further investigations.");
413                             log.println("\t The type seems to be an Any with value of NULL.");
414                             log.println("\t Maybe the property should get it's own test method.");
415                         }
416                         if (resValue != null)
417                         {
418                             if ((!compare(resValue, oldValue)) || (!resValue.equals(oldValue)))
419                             {
420                                 log.println("But it has changed.");
421                                 tRes.tested(propName, true);
422                             }
423                             else
424                             {
425                                 tRes.tested(propName, false);
426                             }
427                         }
428                         else
429                         {
430                             tRes.tested(propName, false);
431                         }
432                         //tRes.tested(propName, false);
433                     }
434                     else
435                     {
436                         log.println("Property '" + propName + "' OK");
437                         try
438                         {
439                             if (!util.utils.isVoid(oldValue) && oldValue instanceof Any)
440                             {
441                                 oldValue = AnyConverter.toObject(new Type(((Any) oldValue).getClass()), oldValue);
442                             }
443 //                            log.println("old = " + toString(oldValue));
444 //                            log.println("new = " + toString(newValue));
445                             log.println("result = " + toString(resValue));
446                         }
447                         catch (com.sun.star.lang.IllegalArgumentException iae)
448                         {
449                         }
450                         tRes.tested(propName, true);
451                     }
452                 }
453                 else
454                 {
455                     throw exception;
456                 }
457             }
458         }
459 
460         /**
461          * The method produces new value of the property from the oldValue.
462          * It returns the result of ValueChanger.changePValue method.
463          * Subclasses can override the method to return their own value,
464          * when the changePValue behavior is not enough, for example,
465          * when oldValue is null.
466          */
getNewValue(String propName, Object oldValue)467         protected Object getNewValue(String propName, Object oldValue)
468                 throws java.lang.IllegalArgumentException
469         {
470             return ValueChanger.changePValue(oldValue);
471         }
472 
473         /**
474          * The method compares obj1 and obj2. It calls
475          * MultiPropertyTest.compare, but subclasses can override to change
476          * the behavior, since normally compare calls Object.equals method
477          * which is not appropriate in some cases (e.g., structs with equals
478          * not overridden).
479          */
compare(Object obj1, Object obj2)480         protected boolean compare(Object obj1, Object obj2)
481         {
482             return callCompare(obj1, obj2);
483         }
484 
485         /**
486          * The method returns a String representation of the obj. It calls
487          * MultipropertyTest.toString(Object), but subclasses can override
488          * to change the behavior.
489          */
toString(Object obj)490         protected String toString(Object obj)
491         {
492             return callToString(obj);
493         }
494     }
495 
496     /**
497      * Extension for <code>PropertyTester</code> which switches two
498      * different values. <code>getNewValue()</code> method of this
499      * class returns one of these two values depending on the
500      * old value, so new value is not equal to old value.
501      */
502     public class PropertyValueSwitcher extends PropertyTester
503     {
504 
505         Object val1 = null;
506         Object val2 = null;
507 
508         /**
509          * Constructs a property tester with two different values
510          * specified as parameters.
511          *
512          * @param val1 Not <code>null</code> value for the property
513          *     tested.
514          * @param val2 Not <code>null</code> value for the property
515          *     tested which differs from the first value.
516          */
PropertyValueSwitcher(Object val1, Object val2)517         public PropertyValueSwitcher(Object val1, Object val2)
518         {
519             this.val1 = val1;
520             this.val2 = val2;
521         }
522 
523         /**
524          * Overridden method of <code>PropertyTester</code> which
525          * returns new value from two values specified.
526          *
527          * @return The second value if old value is equal to the first
528          * one, the first value otherwise.
529          */
getNewValue(String propName, Object old)530         protected Object getNewValue(String propName, Object old)
531         {
532             if (ValueComparer.equalValue(val1, old))
533             {
534                 return val2;
535             }
536             else
537             {
538                 return val1;
539             }
540         }
541     }
542 
543     /**
544      * The method performs testing of propName property using propTester.
545      */
testProperty(String propName, PropertyTester propTester)546     protected void testProperty(String propName, PropertyTester propTester)
547     {
548         propTester.testProperty(propName);
549     }
550 
551     /**
552      * The method performs testing of propName property. It uses PropertyTester
553      * instance for testing.
554      */
testProperty(String propName)555     protected void testProperty(String propName)
556     {
557         testProperty(propName, new PropertyTester());
558     }
559 
560     /**
561      * Tests the property using <code>PropertyValueSwitcher</code>
562      * tester and two values for this property.
563      *
564      * @see PropertyValueSwitcher
565      */
testProperty(String propName, Object val1, Object val2)566     protected void testProperty(String propName, Object val1, Object val2)
567     {
568         testProperty(propName, new PropertyValueSwitcher(val1, val2));
569     }
570 
571     /**
572      * The method just calls compare. This is a workaround to CodeWarrior's
573      * compiler bug.
574      */
callCompare(Object obj1, Object obj2)575     private boolean callCompare(Object obj1, Object obj2)
576     {
577         return compare(obj1, obj2);
578     }
579 
580     /**
581      * Compares two object. In the implementation calls obj1.equals(obj2).
582      */
compare(Object obj1, Object obj2)583     protected boolean compare(Object obj1, Object obj2)
584     {
585         return ValueComparer.equalValue(obj1, obj2);
586     }
587 
588     /**
589      * The method just calls toString. This is a workaround to
590      * CodeWarrior's compiler bug.
591      */
callToString(Object obj)592     private String callToString(Object obj)
593     {
594         return toString(obj);
595     }
596 
597     /**
598      * Gets string representation of the obj. In the implementation
599      * returns obj.toString().
600      */
toString(Object obj)601     protected String toString(Object obj)
602     {
603         return obj == null ? "null" : obj.toString();
604     }
605 }
606