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