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