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