001 /* 002 Copyright (C) 2002-2003 Renaud Pawlak <renaud@aopsys.com>, 003 Laurent Martelli <laurent@aopsys.com> 004 005 This program is free software; you can redistribute it and/or modify 006 it under the terms of the GNU Lesser General Public License as 007 published by the Free Software Foundation; either version 2 of the 008 License, or (at your option) any later version. 009 010 This program is distributed in the hope that it will be useful, 011 but WITHOUT ANY WARRANTY; without even the implied warranty of 012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 GNU Lesser General Public License for more details. 014 015 You should have received a copy of the GNU Lesser General Public License 016 along with this program; if not, write to the Free Software 017 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ 018 019 package org.objectweb.jac.aspects.gui; 020 021 import java.util.Arrays; 022 import java.util.HashMap; 023 import java.util.HashSet; 024 import java.util.Hashtable; 025 import java.util.Iterator; 026 import java.util.List; 027 import java.util.Map; 028 import java.util.Set; 029 import java.util.WeakHashMap; 030 import org.aopalliance.intercept.ConstructorInvocation; 031 import org.aopalliance.intercept.MethodInvocation; 032 import org.apache.log4j.Logger; 033 import org.objectweb.jac.core.AspectComponent; 034 import org.objectweb.jac.core.Interaction; 035 import org.objectweb.jac.core.Wrappee; 036 import org.objectweb.jac.core.Wrapper; 037 import org.objectweb.jac.core.Wrapping; 038 import org.objectweb.jac.core.rtti.CollectionItem; 039 import org.objectweb.jac.core.rtti.FieldItem; 040 import org.objectweb.jac.core.rtti.MemberItem; 041 import org.objectweb.jac.core.rtti.MethodItem; 042 import org.objectweb.jac.util.Strings; 043 044 /** 045 * This wrapper updates the views of a given object when its state 046 * changes, that is to say when a write method is called on the 047 * wrappee.<p> 048 * 049 * A view controller can control several views of the same wrappee at 050 * the same time.<p> 051 * 052 * This mecanism is similar to the famous MVC design pattern. The 053 * model is the wrappee and the controller is the wrapper.<p> 054 * 055 * @see View 056 * @see #registerObject(Wrappee,ObjectUpdate,Object) 057 * @see #registerField(Wrappee,FieldItem,FieldUpdate,Object) 058 * @see #registerCollection(Wrappee,CollectionItem,CollectionUpdate,Object) 059 * @see #unregisterObject(Wrappee,ObjectUpdate) 060 * @see #unregisterField(Wrappee,FieldItem,FieldUpdate) 061 * @see #unregisterCollection(Wrappee,CollectionItem,CollectionUpdate) 062 */ 063 064 public class ViewControlWrapper extends Wrapper { 065 static Logger logger = Logger.getLogger("gui.viewcontrol"); 066 static Logger loggerReg = Logger.getLogger("gui.register"); 067 068 // FieldItem -> (FieldUpdate -> param) (clients to notify for each field) 069 WeakHashMap fieldClients = new WeakHashMap(); 070 // MethodItem -> (MethodUpdate -> param) (clients to notify for each method) 071 WeakHashMap methodClients = new WeakHashMap(); 072 // FieldItem -> (CollectionUpdate -> param) (clients to notify for each collection) 073 WeakHashMap collectionClients = new WeakHashMap(); 074 // ObjectUpdate -> param (clients to notify when the wrappee's state is modified) 075 WeakHashMap objectClients = new WeakHashMap(); 076 077 // FieldItem -> (FieldUpdate -> param) (clients to notify for each field) 078 //Hashtable allFieldClients = new Hashtable(); 079 // MethodItem -> (MethodUpdate -> param) (clients to notify for each method) 080 static Hashtable allMethodClients = new Hashtable(); 081 // FieldItem -> (CollectionUpdate -> param) (clients to notify for each collection) 082 //Hashtable allCollectionClients = new Hashtable(); 083 // ObjectUpdate -> param (clients to notify when the wrappee's state is modified) 084 //HashMap allObjectClients = new HashMap(); 085 086 /** 087 * Creates an empty view control wrapper.<p> 088 */ 089 public ViewControlWrapper(AspectComponent ac) { 090 super(ac); 091 } 092 093 /** 094 * Register for a fieldUpdated event.<p> 095 * 096 * @param wrappee 097 * @param field the field whose updates must be notified 098 * @param client the object to notify when the field is updated 099 * @param param 100 * @see #unregisterField(Wrappee,FieldItem,FieldUpdate) 101 */ 102 public void registerField( 103 Wrappee wrappee, 104 FieldItem field, 105 FieldUpdate client, 106 Object param) { 107 Map clients = (Map) fieldClients.get(field.getName()); 108 if (clients == null) { 109 clients = new HashMap(); 110 fieldClients.put(field.getName(), clients); 111 } 112 clients.put(client, param); 113 loggerReg.debug( 114 "registerField(" + Strings.hex(client) 115 + ") on " + Strings.hex(wrappee) + "." + field.getName()); 116 } 117 118 /** 119 * Unregister from a fieldUpdated event.<p> 120 * 121 * @param field the field whose updates must not be notified anymore 122 * @param client the object not to notify anymore 123 * @see #registerField(Wrappee,FieldItem,FieldUpdate,Object) 124 */ 125 public void unregisterField( 126 Wrappee wrappee, 127 FieldItem field, 128 FieldUpdate client) { 129 Map clients = (Map) fieldClients.get(field.getName()); 130 if (clients != null) { 131 loggerReg.debug("unregisterField(" 132 + Strings.hex(client) 133 + ") on " 134 + Strings.hex(wrappee) 135 + "." 136 + field.getName()); 137 clients.remove(client); 138 } 139 } 140 141 /** 142 * Register for a collectionUpdated event.<p> 143 * 144 * @param collection the collection whose updates must be notified 145 * @param client the object to notify when the field is updated 146 * @see #unregisterCollection(Wrappee,CollectionItem,CollectionUpdate) 147 */ 148 public void registerCollection( 149 Wrappee wrappee, 150 CollectionItem collection, 151 CollectionUpdate client, 152 Object param) { 153 Map clients = (Map) collectionClients.get(collection.getName()); 154 if (clients == null) { 155 clients = new HashMap(); 156 collectionClients.put(collection.getName(), clients); 157 } 158 clients.put(client, param); 159 loggerReg.debug("registerCollection(" 160 + Strings.hex(client) 161 + ") on " 162 + Strings.hex(wrappee) 163 + "." 164 + collection.getName()); 165 } 166 167 /** 168 * Unregister from a collectionUpdated event.<p> 169 * 170 * @param collection the collection whose updates must not be notified anymore 171 * @param client the object not to notify anymore 172 * @see #registerCollection(Wrappee,CollectionItem,CollectionUpdate,Object) 173 */ 174 175 public void unregisterCollection( 176 Wrappee wrappee, 177 CollectionItem collection, 178 CollectionUpdate client) { 179 Map clients = (Map) collectionClients.get(collection.getName()); 180 if (clients != null) { 181 loggerReg.debug("unregisterCollection(" 182 + Strings.hex(client) 183 + ") on " 184 + Strings.hex(wrappee) 185 + "." 186 + collection.getName()); 187 clients.remove(client); 188 } 189 } 190 191 /** 192 * Register for a fieldUpdated event.<p> 193 * 194 * @param wrappee 195 * @param method the method whose updates must be notified 196 * @param client the object to notify when the field is updated 197 * @param param 198 * @see #unregisterField(Wrappee,FieldItem,FieldUpdate) 199 */ 200 public void registerMethod( 201 Wrappee wrappee, 202 MethodItem method, 203 MethodUpdate client, 204 Object param) { 205 String key = method.getFullName(); 206 Map clients = (Map) methodClients.get(key); 207 if (clients == null) { 208 clients = new HashMap(); 209 methodClients.put(key, clients); 210 } 211 clients.put(client, param); 212 213 clients = (Map) allMethodClients.get(key); 214 if (clients == null) { 215 clients = new HashMap(); 216 allMethodClients.put(key, clients); 217 } 218 clients.put(client, param); 219 220 loggerReg.debug("registerMethod(" 221 + Strings.hex(client) 222 + ") on " 223 + Strings.hex(wrappee) 224 + "." 225 + key); 226 } 227 228 public void unregisterMethod( 229 Wrappee wrappee, 230 MethodItem method, 231 MethodUpdate client) { 232 String key = method.getFullName(); 233 Map clients = (Map) methodClients.get(key); 234 if (clients != null) { 235 loggerReg.debug("unregisterMethod(" 236 + Strings.hex(client) 237 + ") on " 238 + Strings.hex(wrappee) 239 + "." 240 + method.getName()); 241 clients.remove(client); 242 } 243 clients = (Map) allMethodClients.get(key); 244 if (clients != null) { 245 clients.remove(client); 246 } 247 } 248 249 /** 250 * Register for an objectUpdated event. 251 * 252 * @param client whom to notify when the wrappee is updated 253 * @param param an object that will be passed back to client on 254 * each notification event. 255 */ 256 public void registerObject( 257 Wrappee wrappee, 258 ObjectUpdate client, 259 Object param) { 260 loggerReg.debug( 261 "registerObject " + Strings.hex(client) + 262 " on " + Strings.hex(wrappee)); 263 objectClients.put(client, param); 264 } 265 266 /** 267 * Unregister from an objectUpdated event. 268 * 269 * @param client whom not to notify anymore 270 */ 271 public void unregisterObject(Wrappee wrappee, ObjectUpdate client) { 272 loggerReg.debug( 273 "unregisterObject(" + Strings.hex(client) 274 + ") on " + Strings.hex(wrappee)); 275 objectClients.remove(client); 276 } 277 278 /** 279 * Unregister a client from all update events 280 * @param wrappee the object to unregister from 281 * @param client the client to unregister 282 */ 283 public void unregister(Wrappee wrappee, Object client) { 284 loggerReg.debug("unregister(" + Strings.hex(client) 285 + ") on " + Strings.hex(wrappee)); 286 objectClients.remove(client); 287 Iterator i; 288 i = fieldClients.values().iterator(); 289 while (i.hasNext()) { 290 Map clients = (Map) i.next(); 291 clients.remove(client); 292 } 293 294 i = collectionClients.values().iterator(); 295 while (i.hasNext()) { 296 Map clients = (Map) i.next(); 297 clients.remove(client); 298 } 299 } 300 301 /** 302 * Get the views controlled by this wrapper.<p> 303 * 304 * @return the set of controlled views 305 */ 306 307 /* 308 public Vector getViews() { 309 return controlledViews; 310 } 311 */ 312 313 /** 314 * A wrapping method that updates the views of the objects.<p> 315 * 316 * It uses the RTTI aspect to know the fields and the collections 317 * that are written, added, or removed by the currently wrapped 318 * method. Then it upcall the <code>refreshStateComponent</code> of 319 * all the controlled views to refresh the implied GUI 320 * components.<p> 321 * 322 * @see org.objectweb.jac.core.rtti 323 * @see ObjectUpdate 324 * @see FieldUpdate 325 * @see CollectionUpdate 326 */ 327 328 public Object updateView(Interaction interaction) { 329 330 Object ret = proceed(interaction); 331 332 logger.debug(this 333 + " checking view updating for method " 334 + Strings.hex(interaction.wrappee) 335 + "." 336 + interaction.method.getName()); 337 338 MethodItem method = (MethodItem) interaction.method; 339 340 /* 341 JTextArea console = (JTextArea) method.getAttribute("Gui.loggingMethod"); 342 if( console != null ) { 343 console.append( (String)arg(0) ); 344 console.setCaretPosition( console.getText().length() ); 345 } 346 */ 347 348 // notify registered clients for fieldUpdated 349 FieldItem[] writtenFields = method.getWrittenFields(); 350 if (writtenFields != null) { 351 Class cl = interaction.getActualClass(); 352 for (int i = 0; i < writtenFields.length; i++) { 353 if (writtenFields[i].getGetter() == interaction.method) { 354 logger.warn( 355 "Skipping " + interaction.method 356 + " since it's the getter of " + writtenFields[i]); 357 continue; 358 } 359 logger.debug(method.getClassItem() + "." + method.getFullName() 360 + " writes " + writtenFields[i].getLongName()); 361 onFieldWrite(interaction.wrappee,cl,writtenFields[i]); 362 363 } 364 } 365 366 // notify registered clients for collectionUpdated 367 CollectionItem[] addedCollections = method.getAddedCollections(); 368 CollectionItem[] removedCollections = method.getRemovedCollections(); 369 CollectionItem[] modifiedCollections = method.getModifiedCollections(); 370 HashSet clientCollections = new HashSet(); 371 if (addedCollections != null) { 372 clientCollections.addAll(Arrays.asList(addedCollections)); 373 } 374 if (removedCollections != null) { 375 clientCollections.addAll(Arrays.asList(removedCollections)); 376 } 377 if (modifiedCollections != null) { 378 clientCollections.addAll(Arrays.asList(modifiedCollections)); 379 } 380 Iterator i = clientCollections.iterator(); 381 while (i.hasNext()) { 382 CollectionItem collection = (CollectionItem) i.next(); 383 HashMap clients = 384 (HashMap) collectionClients.get(collection.getName()); 385 if (clients != null) { 386 Iterator it = ((Map) clients.clone()).keySet().iterator(); 387 Object value = 388 collection.getThroughAccessor(interaction.wrappee); 389 while (it.hasNext()) { 390 CollectionUpdate client = (CollectionUpdate) it.next(); 391 logger.debug("collectionUpdated(" 392 + collection.getLongName() 393 + ") on " 394 + Strings.hex(client)); 395 try { 396 if (method.isAdder()) 397 client.onAdd( 398 interaction.wrappee, 399 collection, 400 value, 401 interaction.args[0], 402 clients.get(client)); 403 else if (method.isRemover()) 404 client.onRemove( 405 interaction.wrappee, 406 collection, 407 value, 408 interaction.args[0], 409 clients.get(client)); 410 else 411 client.onChange( 412 interaction.wrappee, 413 collection, 414 value, 415 clients.get(client)); 416 } catch (Exception e) { 417 logger.error( 418 "Caught exception in collectionUpdated(" 419 + collection.getLongName() 420 + ") on " 421 + Strings.hex(client), 422 e); 423 } 424 } 425 } 426 notifyDependentCollections( 427 collection, 428 interaction.wrappee, 429 method, 430 interaction.args); 431 } 432 433 // notify registered clients for objectUpdated 434 if (method.isModifier()) { 435 logger.debug(method + " is a modifier"); 436 onObjectModified(interaction.wrappee); 437 } 438 439 return ret; 440 } 441 442 public void onObjectModified(Object substance) { 443 Iterator it = (new HashMap(objectClients)).keySet().iterator(); 444 while (it.hasNext()) { 445 ObjectUpdate client = (ObjectUpdate) it.next(); 446 logger.debug("objectUpdated(" 447 + Strings.hex(substance) 448 + ") on " 449 + Strings.hex(client)); 450 try { 451 client.objectUpdated( 452 substance, 453 objectClients.get(client)); 454 } catch (Exception e) { 455 logger.error( 456 "Caught exception in objectUpdated(" 457 + Strings.hex(substance) 458 + ") on " 459 + Strings.hex(client), 460 e); 461 } 462 } 463 } 464 465 /** 466 * @param substance the object whose field is written 467 * @param cl the class of substance 468 * @param writtenField the field which is written 469 */ 470 public void onFieldWrite(Object substance, Class cl, FieldItem writtenField) { 471 logger.debug("onFieldWrite "+substance+"."+writtenField); 472 473 HashMap clients = 474 (HashMap) fieldClients.get(writtenField.getName()); 475 if (clients != null) { 476 Iterator it = ((Map) clients.clone()).entrySet().iterator(); 477 Object value = 478 writtenField.getThroughAccessor(substance); 479 while (it.hasNext()) { 480 Map.Entry entry = (Map.Entry) it.next(); 481 FieldUpdate client = (FieldUpdate) entry.getKey(); 482 logger.debug(" fieldUpdated(" 483 + writtenField.getLongName() 484 + ") on " 485 + Strings.hex(client)); 486 try { 487 client.fieldUpdated( 488 substance, 489 writtenField, 490 value, 491 entry.getValue()); 492 } catch (Exception e) { 493 logger.error( 494 "Caught exception in fieldUpdated(" 495 + writtenField.getLongName() 496 + ") on " 497 + Strings.hex(client), e); 498 } 499 } 500 } 501 502 // Notify dependent fields 503 FieldItem[] dependentFields = 504 writtenField.getDependentFields(); 505 for (int j = 0; j < dependentFields.length; j++) { 506 if (!dependentFields[j] 507 .getClassItem() 508 .getActualClass() 509 .isAssignableFrom(cl)) { 510 logger.debug(" Skipping dependentField " 511 + dependentFields[j] 512 + " (class=" + cl.getName() + ")"); 513 continue; 514 } 515 516 logger.debug(" dependent field " + dependentFields[j].getLongName()); 517 List depSubstances = (List)dependentFields[j].getSubstances(substance); 518 Iterator sit = depSubstances.iterator(); 519 while(sit.hasNext()) { 520 Wrappee depSubstance = (Wrappee)sit.next(); 521 logger.debug(" iterating on " + depSubstance); 522 if (depSubstance!=null && depSubstance!=substance) { 523 Wrapping.invokeRoleMethod( 524 depSubstance, 525 ViewControlWrapper.class, 526 "onFieldWrite", 527 new Object[] {depSubstance,depSubstance.getClass(),dependentFields[j].getField()} 528 ); 529 Wrapping.invokeRoleMethod( 530 depSubstance, 531 ViewControlWrapper.class, 532 "onObjectModified", 533 new Object[] {depSubstance} 534 ); 535 } 536 } 537 clients = 538 (HashMap) fieldClients.get( 539 dependentFields[j].getName()); 540 if (clients != null) { 541 Iterator it = 542 ((Map) clients.clone()).entrySet().iterator(); 543 Object value = 544 dependentFields[j].getThroughAccessor( 545 substance); 546 while (it.hasNext()) { 547 Map.Entry entry = (Map.Entry) it.next(); 548 FieldUpdate client = (FieldUpdate) entry.getKey(); 549 logger.debug(" fieldUpdated(" 550 + dependentFields[j].getLongName() 551 + ") on " + Strings.hex(client)); 552 try { 553 client.fieldUpdated( 554 substance, 555 dependentFields[j], 556 value, 557 entry.getValue()); 558 } catch (Exception e) { 559 logger.error( 560 "Caught exception in fieldUpdated(" 561 + dependentFields[j].getLongName() 562 + ") on " + Strings.hex(client), 563 e); 564 } 565 } 566 } 567 } 568 569 notifyDependentMethods( 570 methodClients, 571 writtenField, 572 substance, 573 cl, 574 new HashSet()); 575 576 } 577 578 579 /** 580 * @param methodClients the clients to notify 581 * @param member member item which caused the notification 582 * @param wrappee object which caused the notification 583 * @param cl class of wrappee or null 584 * @param alreadyNotified already notified clients, so that we can 585 * avoid infinite loops and notifying a client several times 586 */ 587 void notifyDependentMethods( 588 Map methodClients, 589 MemberItem member, 590 Object wrappee, 591 Class cl, 592 Set alreadyNotified) { 593 logger.debug("notifyDependentMethods for " + member + " / " + wrappee); 594 // Notify dependent methods 595 MethodItem[] dependentMethods = member.getDependentMethods(); 596 for (int i = 0; i < dependentMethods.length; i++) { 597 // the class of the dependent method 598 Class depClass = 599 dependentMethods[i].getClassItem().getActualClass(); 600 if ((cl != null && !depClass.isAssignableFrom(cl)) 601 || alreadyNotified.contains(dependentMethods[i])) { 602 logger.debug(" skipping " + dependentMethods[i].getLongName()); 603 continue; 604 } 605 logger.debug("dependent method " 606 + dependentMethods[i].getClassItem() 607 + "." 608 + dependentMethods[i].getFullName()); 609 alreadyNotified.add(dependentMethods[i]); 610 HashMap clients = 611 (HashMap) methodClients.get(dependentMethods[i].getFullName()); 612 if (clients != null) { 613 Iterator it = ((Map) clients.clone()).entrySet().iterator(); 614 while (it.hasNext()) { 615 Map.Entry entry = (Map.Entry) it.next(); 616 MethodUpdate client = (MethodUpdate) entry.getKey(); 617 logger.debug("MethodUpdated(" 618 + dependentMethods[i].getLongName() 619 + ") on " 620 + Strings.hex(client)); 621 try { 622 client.methodUpdated( 623 wrappee, 624 dependentMethods[i], 625 entry.getValue()); 626 } catch (Exception e) { 627 logger.error( 628 "Caught exception in methodUpdated(" 629 + dependentMethods[i].getLongName() 630 + ") on " + Strings.hex(client), 631 e); 632 } 633 } 634 } 635 if (member != dependentMethods[i]) 636 notifyDependentMethods( 637 allMethodClients, 638 dependentMethods[i], 639 wrappee, 640 null, 641 alreadyNotified); 642 } 643 } 644 645 void notifyDependentCollections( 646 CollectionItem collection, 647 Wrappee wrappee, 648 MethodItem method, 649 Object[] args) { 650 // Notify dependent fields 651 FieldItem[] dependentFields = collection.getDependentFields(); 652 for (int j = 0; j < dependentFields.length; j++) { 653 logger.debug("dependent field " 654 + dependentFields[j].getName() 655 + " for " 656 + collection.getName()); 657 HashMap clients = 658 (HashMap) collectionClients.get(dependentFields[j].getName()); 659 if (clients != null) { 660 Iterator it2 = ((Map) clients.clone()).entrySet().iterator(); 661 Object value = dependentFields[j].getThroughAccessor(wrappee); 662 while (it2.hasNext()) { 663 Map.Entry entry = (Map.Entry) it2.next(); 664 CollectionUpdate client = (CollectionUpdate) entry.getKey(); 665 logger.debug( 666 "collectionUpdated(" + dependentFields[j].getLongName() 667 + ") on " + Strings.hex(client)); 668 try { 669 client.onChange( 670 wrappee, 671 (CollectionItem) dependentFields[j], 672 value, 673 entry.getValue()); 674 } catch (Exception e) { 675 logger.error( 676 "Caught exception in collectionUpdated(" 677 + dependentFields[j].getLongName() 678 + ") on " + Strings.hex(client), 679 e); 680 } 681 } 682 } 683 } 684 } 685 686 public Object invoke(MethodInvocation invocation) throws Throwable { 687 return updateView((Interaction) invocation); 688 } 689 690 public Object construct(ConstructorInvocation invocation) 691 throws Throwable { 692 throw new Exception("This wrapper does not support constructor wrapping"); 693 } 694 695 }