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