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 ifc.beans; 25 26 import java.util.Vector; 27 28 import lib.MultiMethodTest; 29 import util.ValueChanger; 30 import util.utils; 31 32 import com.sun.star.beans.Property; 33 import com.sun.star.beans.PropertyAttribute; 34 import com.sun.star.beans.PropertyChangeEvent; 35 import com.sun.star.beans.XPropertyChangeListener; 36 import com.sun.star.beans.XPropertySet; 37 import com.sun.star.beans.XPropertySetInfo; 38 import com.sun.star.beans.XVetoableChangeListener; 39 import com.sun.star.lang.EventObject; 40 41 /** 42 * Testing <code>com.sun.star.beans.XPropertySet</code> 43 * interface methods : 44 * <ul> 45 * <li><code>getPropertySetInfo()</code></li> 46 * <li><code>setPropertyValue()</code></li> 47 * <li><code>getPropertyValue()</code></li> 48 * <li><code>addPropertyChangeListener()</code></li> 49 * <li><code>removePropertyChangeListener()</code></li> 50 * <li><code>addVetoableChangeListener()</code></li> 51 * <li><code>removeVetoableChangeListener()</code></li> 52 * </ul> 53 * @see com.sun.star.beans.XPropertySet 54 */ 55 public class _XPropertySet extends MultiMethodTest { 56 57 public XPropertySet oObj = null; 58 59 /** 60 * Flag that indicates change listener was called. 61 */ 62 private boolean propertyChanged = false; 63 64 /** 65 * Listener that must be called on bound property changing. 66 */ 67 public class MyChangeListener implements XPropertyChangeListener { 68 /** 69 * Just set <code>propertyChanged</code> flag to true. 70 */ propertyChange(PropertyChangeEvent e)71 public void propertyChange(PropertyChangeEvent e) { 72 propertyChanged = true; 73 } disposing(EventObject obj)74 public void disposing (EventObject obj) {} 75 } 76 77 private final XPropertyChangeListener PClistener = new MyChangeListener(); 78 79 /** 80 * Flag that indicates veto listener was called. 81 */ 82 private boolean vetoableChanged = false; 83 84 /** 85 * Listener that must be called on constrained property changing. 86 */ 87 public class MyVetoListener implements XVetoableChangeListener { 88 /** 89 * Just set <code>vetoableChanged</code> flag to true. 90 */ vetoableChange(PropertyChangeEvent e)91 public void vetoableChange(PropertyChangeEvent e) { 92 vetoableChanged = true; 93 } disposing(EventObject obj)94 public void disposing (EventObject obj) {} 95 } 96 97 private final XVetoableChangeListener VClistener = new MyVetoListener(); 98 99 /** 100 * Structure that collects the properties of different types to test : 101 * Constrained, Bound and Normal. 102 */ 103 private final class PropsToTest { 104 Vector< String > constrained = new Vector< String >(); 105 Vector< String > bound = new Vector< String >(); 106 Vector< String > normal = new Vector< String >(); 107 } 108 109 private final PropsToTest PTT = new PropsToTest(); 110 111 /** 112 * Tests method <code>getPropertySetInfo</code>. After test completed 113 * call {@link #getPropsToTest} method to retrieve different kinds 114 * of properties to test then. <p> 115 * Has OK status if not null <code>XPropertySetInfo</code> 116 * object returned.<p> 117 * Since <code>getPropertySetInfo</code> is optional, it may return null, 118 * if it is not implemented. This method uses then an object relation 119 * <code>PTT</code> (Properties To Test) to determine available properties. 120 * All tests for services without <code>getPropertySetInfo</code> must 121 * provide this object relation. 122 */ _getPropertySetInfo()123 public void _getPropertySetInfo() { 124 125 XPropertySetInfo propertySetInfo = oObj.getPropertySetInfo(); 126 127 if (propertySetInfo == null) { 128 log.println("getPropertySetInfo() method returned null"); 129 tRes.tested("getPropertySetInfo()", true) ; 130 String[] ptt = (String[]) tEnv.getObjRelation("PTT"); 131 PTT.normal.clear(); 132 PTT.bound.clear(); 133 PTT.constrained.clear(); 134 PTT.normal.add( ptt[0] ); 135 PTT.bound.add( ptt[1] ); 136 PTT.constrained.add( ptt[2] ); 137 } else { 138 tRes.tested("getPropertySetInfo()", true ); 139 getPropsToTest(propertySetInfo); 140 } 141 142 return; 143 144 } // end of getPropertySetInfo() 145 146 /** 147 * Tests change listener which added for bound properties. 148 * Adds listener to bound property (if it exists), then changes 149 * its value and check if listener was called. <p> 150 * Method tests to be successfully completed before : 151 * <ul> 152 * <li> <code>getPropertySetInfo</code> : in this method test 153 * one of bound properties is retrieved. </li> 154 * </ul> <p> 155 * Has OK status if NO bound properties exist or if listener 156 * was successfully called. 157 */ _addPropertyChangeListener()158 public void _addPropertyChangeListener() { 159 160 requiredMethod("getPropertySetInfo()"); 161 162 int count = PTT.bound.size(); 163 if ( count==0 || PTT.bound.get(0).equals("none") ) { 164 log.println("*** No bound properties found ***"); 165 tRes.tested("addPropertyChangeListener()", true) ; 166 } else { 167 boolean error = false; 168 for (int i = 0; i < count; i++) { 169 String propertyName = PTT.bound.get(i); 170 propertyChanged = false; 171 try { 172 oObj.addPropertyChangeListener(propertyName,PClistener); 173 Object gValue = oObj.getPropertyValue(propertyName); 174 log.println("Check bound property: " + propertyName ); 175 oObj.setPropertyValue(propertyName, 176 ValueChanger.changePValue(gValue)); 177 } catch (com.sun.star.beans.PropertyVetoException e) { 178 log.println("Exception occured while trying to change "+ 179 "property '"+ propertyName+"'"); 180 e.printStackTrace(log); 181 } catch (com.sun.star.lang.IllegalArgumentException e) { 182 log.println("Exception occured while trying to change "+ 183 "property '"+ propertyName+"'"); 184 e.printStackTrace(log); 185 } catch (com.sun.star.beans.UnknownPropertyException e) { 186 log.println("Exception occured while trying to change "+ 187 "property '"+ propertyName+"'"); 188 e.printStackTrace(log); 189 } catch (com.sun.star.lang.WrappedTargetException e) { 190 log.println("Exception occured while trying to change "+ 191 "property '"+ propertyName+"'"); 192 e.printStackTrace(log); 193 } // end of try-catch 194 error = error || !propertyChanged; 195 if (!propertyChanged) { 196 log.println("propertyChangeListener wasn't called for '"+ 197 propertyName+"'"); 198 } 199 } 200 tRes.tested("addPropertyChangeListener()", !error); 201 } 202 203 return; 204 205 } // end of addPropertyChangeListener() 206 207 /** 208 * Tests vetoable listener which added for constrained properties. 209 * Adds listener to constrained property (if it exists), then changes 210 * its value and check if listener was called. <p> 211 * Method tests to be successfully completed before : 212 * <ul> 213 * <li> <code>getPropertySetInfo</code> : in this method test 214 * one of constrained properties is retrieved. </li> 215 * </ul> <p> 216 * Has OK status if NO constrained properties exist or if listener 217 * was successfully called. 218 */ _addVetoableChangeListener()219 public void _addVetoableChangeListener() { 220 221 requiredMethod("getPropertySetInfo()"); 222 223 int count = PTT.constrained.size(); 224 if ( count==0 || PTT.constrained.get(0).equals("none") ) { 225 log.println("*** No constrained properties found ***"); 226 tRes.tested("addVetoableChangeListener()", true) ; 227 } else { 228 boolean error = false; 229 for (int i = 0; i < count; i++) { 230 String propertyName = PTT.constrained.get(i); 231 vetoableChanged = false; 232 try { 233 oObj.addVetoableChangeListener(propertyName,VClistener); 234 Object gValue = oObj.getPropertyValue(propertyName); 235 oObj.setPropertyValue(propertyName, 236 ValueChanger.changePValue(gValue)); 237 } catch (com.sun.star.beans.PropertyVetoException e) { 238 log.println("Exception occured while trying to change "+ 239 "property '"+ propertyName+"'"); 240 e.printStackTrace(log); 241 } catch (com.sun.star.lang.IllegalArgumentException e) { 242 log.println("Exception occured while trying to change "+ 243 "property '"+ propertyName+"'"); 244 e.printStackTrace(log); 245 } catch (com.sun.star.beans.UnknownPropertyException e) { 246 log.println("Exception occured while trying to change "+ 247 "property '"+ propertyName+"'"); 248 e.printStackTrace(log); 249 } catch (com.sun.star.lang.WrappedTargetException e) { 250 log.println("Exception occured while trying to change "+ 251 "property '"+ propertyName+"'"); 252 e.printStackTrace(log); 253 } // end of try-catch 254 error = error || !vetoableChanged; 255 if (!vetoableChanged) { 256 log.println("vetoableChangeListener wasn't called for '"+ 257 propertyName+"'"); 258 } 259 } 260 tRes.tested("addVetoableChangeListener()",!error); 261 } 262 263 return; 264 265 } // end of addVetoableChangeListener() 266 267 268 /** 269 * Tests <code>setPropertyValue</code> method. 270 * Stores value before call, and compares it with value after 271 * call. <p> 272 * Method tests to be successfully completed before : 273 * <ul> 274 * <li> <code>getPropertySetInfo</code> : in this method test 275 * one of normal properties is retrieved. </li> 276 * </ul> <p> 277 * Has OK status if NO normal properties exist or if value before 278 * method call is not equal to value after. 279 */ _setPropertyValue()280 public void _setPropertyValue() { 281 282 requiredMethod("getPropertySetInfo()"); 283 284 Object gValue = null; 285 Object sValue = null; 286 287 int count = PTT.normal.size(); 288 if ( count==0 || PTT.normal.get(0).equals("none") ) { 289 log.println("*** No changeable properties found ***"); 290 tRes.tested("setPropertyValue()", true) ; 291 } else { 292 boolean error = false; 293 for (int i = 0; i < count; i++) { 294 String propertyName = PTT.normal.get(i); 295 try { 296 log.println("try to change value of property '" + propertyName + "'" ); 297 gValue = oObj.getPropertyValue(propertyName); 298 sValue = ValueChanger.changePValue(gValue); 299 oObj.setPropertyValue(propertyName, sValue); 300 sValue = oObj.getPropertyValue(propertyName); 301 } catch (com.sun.star.beans.PropertyVetoException e) { 302 log.println("Exception occured while trying to change "+ 303 "property '"+ propertyName+"'"); 304 e.printStackTrace(log); 305 } catch (com.sun.star.lang.IllegalArgumentException e) { 306 log.println("Exception occured while trying to change "+ 307 "property '"+ propertyName+"'"); 308 e.printStackTrace(log); 309 } catch (com.sun.star.beans.UnknownPropertyException e) { 310 log.println("Exception occured while trying to change "+ 311 "property '"+ propertyName+"'"); 312 e.printStackTrace(log); 313 } catch (com.sun.star.lang.WrappedTargetException e) { 314 log.println("Exception occured while trying to change "+ 315 "property '"+ propertyName+"'"); 316 e.printStackTrace(log); 317 } // end of try-catch 318 if( gValue.equals(sValue) ) 319 { 320 log.println("setting property '"+ propertyName+"' failed"); 321 error = true; 322 } 323 } 324 tRes.tested("setPropertyValue()",!error); 325 } //endif 326 327 return; 328 329 } // end of setPropertyValue() 330 331 /** 332 * Tests <code>getPropertyValue</code> method for the given property. 333 * Returns true if no exceptions occured 334 */ getSinglePropertyValue( String propertyName )335 private boolean getSinglePropertyValue( String propertyName ) 336 { 337 boolean runOk = false; 338 try { 339 oObj.getPropertyValue(propertyName); 340 runOk = true; 341 } catch (com.sun.star.beans.UnknownPropertyException e) { 342 log.println("Exception occured while trying to get property '"+ 343 propertyName+"'"); 344 e.printStackTrace(log); 345 } catch (com.sun.star.lang.WrappedTargetException e) { 346 log.println("Exception occured while trying to get property '"+ 347 propertyName+"'"); 348 e.printStackTrace(log); 349 } 350 return runOk; 351 } 352 353 /** 354 * Tests <code>getPropertyValue</code> method. 355 * Just call this method and checks for no exceptions <p> 356 * Method tests to be successfully completed before : 357 * <ul> 358 * <li> <code>getPropertySetInfo</code> : in this method test 359 * one of normal properties is retrieved. </li> 360 * </ul> <p> 361 * Has OK status if NO normal properties exist or if no 362 * exceptions were thrown. 363 */ _getPropertyValue()364 public void _getPropertyValue() { 365 366 requiredMethod("getPropertySetInfo()"); 367 368 int count = PTT.normal.size(); 369 if ( count==0 || PTT.normal.get(0).equals("none") ) { 370 Property[] properties = oObj.getPropertySetInfo().getProperties(); 371 if( properties.length > 0 ) { 372 String propertyName = properties[0].Name; 373 log.println("All properties are Read Only"); 374 log.println("Using: "+propertyName); 375 tRes.tested("getPropertyValue()", getSinglePropertyValue( propertyName ) ); 376 } 377 else { 378 log.println("*** No properties found ***"); 379 tRes.tested("getPropertyValue()", true) ; 380 } 381 } else { 382 boolean error = false; 383 for (int i = 0; i < count; i++) { 384 String propertyName = PTT.normal.get(i); 385 boolean runOk = getSinglePropertyValue( propertyName ); 386 if( !runOk ) 387 { 388 error = true; 389 log.println("getPropertyValue() failed for property '"+propertyName+"'"); 390 } 391 } 392 tRes.tested("getPropertyValue()", !error) ; 393 } 394 395 return; 396 } 397 398 /** 399 * Tests <code>removePropertyChangeListener</code> method. 400 * Removes change listener, then changes bound property value 401 * and checks if the listener was NOT called. 402 * Method tests to be successfully completed before : 403 * <ul> 404 * <li> <code>addPropertyChangeListener</code> : here listener 405 * was added. </li> 406 * </ul> <p> 407 * Has OK status if NO bound properties exist or if listener 408 * was not called and no exceptions arose. 409 */ _removePropertyChangeListener()410 public void _removePropertyChangeListener() { 411 412 requiredMethod("addPropertyChangeListener()"); 413 414 int count = PTT.bound.size(); 415 if ( count==0 || PTT.bound.get(0).equals("none") ) { 416 log.println("*** No bound properties found ***"); 417 tRes.tested("removePropertyChangeListener()", true) ; 418 } else { 419 420 //remove all listeners first 421 for (int i = 0; i < count; i++) { 422 String propertyName = PTT.bound.get(i); 423 try { 424 oObj.removePropertyChangeListener(propertyName,PClistener); 425 } catch (Exception e) { 426 log.println("Exception occured while removing change listener from"+ 427 "property '"+ propertyName+"'"); 428 e.printStackTrace(log); 429 } 430 } 431 432 boolean error = false; 433 for (int i = 0; i < count; i++) { 434 String propertyName = PTT.bound.get(i); 435 try { 436 propertyChanged = false; 437 oObj.addPropertyChangeListener(propertyName,PClistener); 438 oObj.removePropertyChangeListener(propertyName,PClistener); 439 Object gValue = oObj.getPropertyValue(propertyName); 440 oObj.setPropertyValue(propertyName, 441 ValueChanger.changePValue(gValue)); 442 } catch (com.sun.star.beans.PropertyVetoException e) { 443 log.println("Exception occured while trying to change "+ 444 "property '"+ propertyName+"'"); 445 e.printStackTrace(log); 446 } catch (com.sun.star.lang.IllegalArgumentException e) { 447 log.println("Exception occured while trying to change "+ 448 "property '"+ propertyName+"'"); 449 e.printStackTrace(log); 450 } catch (com.sun.star.beans.UnknownPropertyException e) { 451 log.println("Exception occured while trying to change "+ 452 "property '"+ propertyName+"'"); 453 e.printStackTrace(log); 454 } catch (com.sun.star.lang.WrappedTargetException e) { 455 log.println("Exception occured while trying to change "+ 456 "property '"+ propertyName+"'"); 457 e.printStackTrace(log); 458 } // end of try-catch 459 460 error = error || propertyChanged; 461 if (propertyChanged) { 462 log.println("propertyChangeListener was called after removing"+ 463 " for '"+propertyName+"'"); 464 } 465 } 466 tRes.tested("removePropertyChangeListener()",!error); 467 } 468 469 return; 470 471 } // end of removePropertyChangeListener() 472 473 474 /** 475 * Tests <code>removeVetoableChangeListener</code> method. 476 * Removes vetoable listener, then changes constrained property value 477 * and checks if the listener was NOT called. 478 * Method tests to be successfully completed before : 479 * <ul> 480 * <li> <code>addPropertyChangeListener</code> : here vetoable listener 481 * was added. </li> 482 * </ul> <p> 483 * Has OK status if NO constrained properties exist or if listener 484 * was NOT called and no exceptions arose. 485 */ _removeVetoableChangeListener()486 public void _removeVetoableChangeListener() { 487 488 requiredMethod("addVetoableChangeListener()"); 489 490 int count = PTT.constrained.size(); 491 if ( count==0 || PTT.constrained.get(0).equals("none") ) { 492 log.println("*** No constrained properties found ***"); 493 tRes.tested("removeVetoableChangeListener()", true) ; 494 } else { 495 496 //remove all listeners first 497 for (int i = 0; i < count; i++) { 498 String propertyName = PTT.constrained.get(i); 499 try { 500 oObj.removeVetoableChangeListener(propertyName,VClistener); 501 } catch (Exception e) { 502 log.println("Exception occured while removing veto listener from"+ 503 "property '"+ propertyName+"'"); 504 e.printStackTrace(log); 505 } 506 } 507 508 boolean error = false; 509 for (int i = 0; i < count; i++) { 510 String propertyName = PTT.constrained.get(i); 511 vetoableChanged = false; 512 try { 513 oObj.addVetoableChangeListener(propertyName,VClistener); 514 oObj.removeVetoableChangeListener(propertyName,VClistener); 515 Object gValue = oObj.getPropertyValue(propertyName); 516 oObj.setPropertyValue(propertyName, 517 ValueChanger.changePValue(gValue)); 518 } catch (com.sun.star.beans.PropertyVetoException e) { 519 log.println("Exception occured while trying to change "+ 520 "property '"+ propertyName+"'"); 521 e.printStackTrace(log); 522 } catch (com.sun.star.lang.IllegalArgumentException e) { 523 log.println("Exception occured while trying to change "+ 524 "property '"+ propertyName+"'"); 525 e.printStackTrace(log); 526 } catch (com.sun.star.beans.UnknownPropertyException e) { 527 log.println("Exception occured while trying to change "+ 528 "property '"+ propertyName+"'"); 529 e.printStackTrace(log); 530 } catch (com.sun.star.lang.WrappedTargetException e) { 531 log.println("Exception occured while trying to change "+ 532 "property '"+ propertyName+"'"); 533 e.printStackTrace(log); 534 } // end of try-catch 535 error = error || vetoableChanged; 536 if (vetoableChanged) { 537 log.println("vetoableChangeListener was called after "+ 538 "removing for '"+propertyName+"'"); 539 } 540 } 541 tRes.tested("removeVetoableChangeListener()",!error); 542 } 543 544 return; 545 546 } // end of removeVetoableChangeListener() 547 548 /** 549 * Gets the properties being tested. Searches and stores by one 550 * property of each kind (Bound, Vetoable, Normal). 551 */ getPropsToTest(XPropertySetInfo xPSI)552 public void getPropsToTest(XPropertySetInfo xPSI) { 553 554 Property[] properties = xPSI.getProperties(); 555 // some properties should not be changed in a unspecific way 556 String[] skip = {"PrinterName", "CharRelief", "IsLayerMode"}; 557 558 for (int i = 0; i < properties.length; i++) { 559 560 Property property = properties[i]; 561 String name = property.Name; 562 563 boolean cont = false; 564 for (int j = 0; j < skip.length; j++) { 565 if (name.equals(skip[j])){ 566 log.println("skipping '" + name + "'"); 567 cont = true; 568 } 569 } 570 571 if (cont) continue; 572 573 if (name.equals(oObj)) 574 log.println("Checking '"+name+"'"); 575 boolean isWritable = ((property.Attributes & 576 PropertyAttribute.READONLY) == 0); 577 boolean isNotNull = ((property.Attributes & 578 PropertyAttribute.MAYBEVOID) == 0); 579 boolean isBound = ((property.Attributes & 580 PropertyAttribute.BOUND) != 0); 581 boolean isConstr = ((property.Attributes & 582 PropertyAttribute.CONSTRAINED) != 0); 583 boolean canChange = false; 584 585 if ( !isWritable ) log.println("Property '"+name+"' is READONLY"); 586 587 if (name.endsWith("URL")) isWritable = false; 588 if (name.startsWith("Fill")) isWritable = false; 589 if (name.startsWith("Font")) isWritable = false; 590 if (name.startsWith("IsNumbering")) isWritable = false; 591 if (name.startsWith("LayerName")) isWritable = false; 592 if (name.startsWith("Line")) isWritable = false; 593 if (name.startsWith("TextWriting")) isWritable = false; 594 595 //if (name.equals("xinterfaceA") || name.equals("xtypeproviderA") 596 //|| name.equals("arAnyA")) isWritable=false; 597 598 if ( isWritable && isNotNull ) canChange = isChangeable(name); 599 600 if ( isWritable && isNotNull && isBound && canChange) { 601 PTT.bound.add(name); 602 } 603 604 if ( isWritable && isNotNull && isConstr && canChange) { 605 PTT.constrained.add(name); 606 } 607 608 if ( isWritable && isNotNull && canChange) { 609 PTT.normal.add(name); 610 } 611 612 613 } // endfor 614 } 615 isChangeable(String name)616 public boolean isChangeable(String name) { 617 boolean hasChanged = false; 618 try { 619 Object getProp = oObj.getPropertyValue(name); 620 log.println("Getting: "+getProp); 621 if (name.equals("xinterfaceA")) { 622 System.out.println("drin"); 623 } 624 625 Object setValue = null; 626 if (getProp != null) { 627 if (!utils.isVoid(getProp)) 628 setValue = ValueChanger.changePValue(getProp); 629 else log.println("Property '"+name+ 630 "' is void but MAYBEVOID isn't set"); 631 } else log.println("Property '"+name+"'is null and can't be changed"); 632 if (name.equals("LineStyle")) setValue = null; 633 if (setValue != null) { 634 log.println("Setting to :"+setValue); 635 oObj.setPropertyValue(name, setValue); 636 hasChanged = (! getProp.equals(oObj.getPropertyValue(name))); 637 } else log.println("Couldn't change Property '"+name+"'"); 638 } catch (com.sun.star.beans.PropertyVetoException e) { 639 log.println("'" + name + "' throws exception '" + e + "'"); 640 e.printStackTrace(log); 641 } catch (com.sun.star.lang.IllegalArgumentException e) { 642 log.println("'" + name + "' throws exception '" + e + "'"); 643 e.printStackTrace(log); 644 } catch (com.sun.star.beans.UnknownPropertyException e) { 645 log.println("'" + name + "' throws exception '" + e + "'"); 646 e.printStackTrace(log); 647 } catch (com.sun.star.lang.WrappedTargetException e) { 648 log.println("'" + name + "' throws exception '" + e + "'"); 649 e.printStackTrace(log); 650 } catch (com.sun.star.uno.RuntimeException e) { 651 log.println("'" + name + "' throws exception '" + e + "'"); 652 e.printStackTrace(log); 653 } catch (java.lang.ArrayIndexOutOfBoundsException e) { 654 log.println("'" + name + "' throws exception '" + e + "'"); 655 e.printStackTrace(log); 656 } 657 658 return hasChanged; 659 } 660 661 /** 662 * Forces environment recreation. 663 */ after()664 protected void after() { 665 disposeEnvironment(); 666 } 667 668 669 } // finish class _XPropertySet 670 671