1 3 package org.jgroups.blocks; 4 5 6 import org.apache.commons.logging.Log; 7 import org.apache.commons.logging.LogFactory; 8 import org.jgroups.*; 9 import org.jgroups.util.Queue; 10 import org.jgroups.util.QueueClosedException; 11 import org.jgroups.util.Util; 12 13 import java.io.Serializable ; 14 import java.util.*; 15 16 17 18 19 26 public class ReplicatedTree implements Runnable , MessageListener, MembershipListener { 27 public static final String SEPARATOR="/"; 28 final static int INDENT=4; 29 Node root=new Node(SEPARATOR, SEPARATOR, null, null); 30 final Vector listeners=new Vector(); 31 final Queue request_queue=new Queue(); 32 Thread request_handler=null; 33 JChannel channel=null; 34 PullPushAdapter adapter=null; 35 String groupname="ReplicatedTree-Group"; 36 final Vector members=new Vector(); 37 long state_fetch_timeout=10000; 38 39 protected final Log log=LogFactory.getLog(this.getClass()); 40 41 42 45 boolean remote_calls=true; 46 String props="UDP(mcast_addr=224.0.0.36;mcast_port=55566;ip_ttl=32;" + 47 "mcast_send_buf_size=150000;mcast_recv_buf_size=80000):" + 48 "PING(timeout=2000;num_initial_members=3):" + 49 "MERGE2(min_interval=5000;max_interval=10000):" + 50 "FD_SOCK:" + 51 "VERIFY_SUSPECT(timeout=1500):" + 52 "pbcast.STABLE(desired_avg_gossip=20000):" + 53 "pbcast.NAKACK(gc_lag=50;retransmit_timeout=600,1200,2400,4800):" + 54 "UNICAST(timeout=5000):" + 55 "FRAG(frag_size=16000;down_thread=false;up_thread=false):" + 56 "pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;" + 57 "shun=false;print_local_addr=true):" + 58 "pbcast.STATE_TRANSFER"; 59 61 63 private boolean send_message = false; 64 65 66 67 public interface ReplicatedTreeListener { 68 void nodeAdded(String fqn); 69 70 void nodeRemoved(String fqn); 71 72 void nodeModified(String fqn); 73 74 void viewChange(View new_view); } 76 77 78 82 public ReplicatedTree(String groupname, String props, long state_fetch_timeout) throws Exception { 83 if(groupname != null) 84 this.groupname=groupname; 85 if(props != null) 86 this.props=props; 87 this.state_fetch_timeout=state_fetch_timeout; 88 channel=new JChannel(this.props); 89 channel.connect(this.groupname); 90 start(); 91 } 92 93 public ReplicatedTree() { 94 } 95 96 97 100 public ReplicatedTree(JChannel channel) throws Exception { 101 this.channel=channel; 102 start(); 103 } 104 105 106 public void setRemoteCalls(boolean flag) { 107 remote_calls=flag; 108 } 109 110 public void setRootNode(Node n) { 111 root=n; 112 } 113 114 public Address getLocalAddress() { 115 return channel != null? channel.getLocalAddress() : null; 116 } 117 118 public Vector getMembers() { 119 return members; 120 } 121 122 123 126 public void fetchState(long timeout) throws ChannelClosedException, ChannelNotConnectedException { 127 boolean rc=channel.getState(null, timeout); 128 if(rc) 129 if(log.isInfoEnabled()) log.info("state was retrieved successfully"); 130 else 131 if(log.isInfoEnabled()) log.info("state could not be retrieved (first member)"); 132 } 133 134 135 public void addReplicatedTreeListener(ReplicatedTreeListener listener) { 136 if(!listeners.contains(listener)) 137 listeners.addElement(listener); 138 } 139 140 141 public void removeReplicatedTreeListener(ReplicatedTreeListener listener) { 142 listeners.removeElement(listener); 143 } 144 145 146 public void start() throws Exception { 147 if(request_handler == null) { 148 request_handler=new Thread (this, "ReplicatedTree.RequestHandler thread"); 149 request_handler.setDaemon(true); 150 request_handler.start(); 151 } 152 adapter=new PullPushAdapter(channel, this, this); 153 adapter.setListener(this); 154 channel.setOpt(Channel.GET_STATE_EVENTS, Boolean.TRUE); 155 boolean rc=channel.getState(null, state_fetch_timeout); 156 if(rc) 157 if(log.isInfoEnabled()) log.info("state was retrieved successfully"); 158 else 159 if(log.isInfoEnabled()) log.info("state could not be retrieved (first member)"); 160 } 161 162 163 public void stop() { 164 if(request_handler != null && request_handler.isAlive()) { 165 request_queue.close(true); 166 request_handler=null; 167 } 168 169 request_handler=null; 170 if(channel != null) { 171 channel.close(); 172 } 173 if(adapter != null) { 174 adapter.stop(); 175 adapter=null; 176 } 177 channel=null; 178 } 179 180 181 189 public void put(String fqn, HashMap data) { 190 if(!remote_calls) { 191 _put(fqn, data); 192 return; 193 } 194 195 if(send_message == true) { 198 if(channel == null) { 199 if(log.isErrorEnabled()) log.error("channel is null, cannot broadcast PUT request"); 200 return; 201 } 202 try { 203 channel.send( 204 new Message( 205 null, 206 null, 207 new Request(Request.PUT, fqn, data))); 208 } 209 catch(Exception ex) { 210 if(log.isErrorEnabled()) log.error("failure bcasting PUT request: " + ex); 211 } 212 } 213 else { 214 _put(fqn, data); 215 } 216 } 217 218 219 227 public void put(String fqn, String key, Object value) { 228 if(!remote_calls) { 229 _put(fqn, key, value); 230 return; 231 } 232 233 if(send_message == true) { 236 237 if(channel == null) { 238 if(log.isErrorEnabled()) log.error("channel is null, cannot broadcast PUT request"); 239 return; 240 } 241 try { 242 channel.send( 243 new Message( 244 null, 245 null, 246 new Request(Request.PUT, fqn, key, value))); 247 } 248 catch(Exception ex) { 249 if(log.isErrorEnabled()) log.error("failure bcasting PUT request: " + ex); 250 } 251 } 252 else { 253 _put(fqn, key, value); 254 } 255 } 256 257 258 262 public void remove(String fqn) { 263 if(!remote_calls) { 264 _remove(fqn); 265 return; 266 } 267 if(send_message == true) { 270 if(channel == null) { 271 if(log.isErrorEnabled()) log.error("channel is null, cannot broadcast REMOVE request"); 272 return; 273 } 274 try { 275 channel.send( 276 new Message(null, null, new Request(Request.REMOVE, fqn))); 277 } 278 catch(Exception ex) { 279 if(log.isErrorEnabled()) log.error("failure bcasting REMOVE request: " + ex); 280 } 281 } 282 else { 283 _remove(fqn); 284 } 285 } 286 287 288 293 public void remove(String fqn, String key) { 294 if(!remote_calls) { 295 _remove(fqn, key); 296 return; 297 } 298 if(send_message == true) { 301 if(channel == null) { 302 if(log.isErrorEnabled()) log.error("channel is null, cannot broadcast REMOVE request"); 303 return; 304 } 305 try { 306 channel.send( 307 new Message( 308 null, 309 null, 310 new Request(Request.REMOVE, fqn, key))); 311 } 312 catch(Exception ex) { 313 if(log.isErrorEnabled()) log.error("failure bcasting REMOVE request: " + ex); 314 } 315 } 316 else { 317 _remove(fqn, key); 318 } 319 } 320 321 322 327 public boolean exists(String fqn) { 328 if(fqn == null) return false; 329 return findNode(fqn) != null? true : false; 330 } 331 332 333 339 public Set getKeys(String fqn) { 340 Node n=findNode(fqn); 341 Map data; 342 343 if(n == null) return null; 344 data=n.getData(); 345 if(data == null) return null; 346 return data.keySet(); 347 } 348 349 350 356 public Object get(String fqn, String key) { 357 Node n=findNode(fqn); 358 359 if(n == null) return null; 360 return n.getData(key); 361 } 362 363 364 371 HashMap get(String fqn) { 372 Node n=findNode(fqn); 373 374 if(n == null) return null; 375 return n.getData(); 376 } 377 378 379 383 public String print(String fqn) { 384 Node n=findNode(fqn); 385 if(n == null) return null; 386 return n.toString(); 387 } 388 389 390 395 public Set getChildrenNames(String fqn) { 396 Node n=findNode(fqn); 397 Map m; 398 399 if(n == null) return null; 400 m=n.getChildren(); 401 if(m != null) 402 return m.keySet(); 403 else 404 return null; 405 } 406 407 408 public String toString() { 409 StringBuffer sb=new StringBuffer (); 410 int indent=0; 411 Map children; 412 413 children=root.getChildren(); 414 if(children != null && children.size() > 0) { 415 Collection nodes=children.values(); 416 for(Iterator it=nodes.iterator(); it.hasNext();) { 417 ((Node)it.next()).print(sb, indent); 418 sb.append('\n'); 419 } 420 } 421 else 422 sb.append(SEPARATOR); 423 return sb.toString(); 424 } 425 426 430 public String getGroupName() {return groupname;} 431 432 436 public Channel getChannel() {return channel;} 437 438 442 public int getGroupMembersNumber() {return members.size();} 443 444 445 446 447 448 449 450 public void _put(String fqn, HashMap data) { 451 Node n; 452 StringHolder child_name=new StringHolder(); 453 boolean child_exists=false; 454 455 if(fqn == null) return; 456 n=findParentNode(fqn, child_name, true); if(child_name.getValue() != null) { 458 child_exists=n.childExists(child_name.getValue()); 459 n.createChild(child_name.getValue(), fqn, n, data); 460 } 461 else { 462 child_exists=true; 463 n.setData(data); 464 } 465 if(child_exists) 466 notifyNodeModified(fqn); 467 else 468 notifyNodeAdded(fqn); 469 } 470 471 472 public void _put(String fqn, String key, Object value) { 473 Node n; 474 StringHolder child_name=new StringHolder(); 475 boolean child_exists=false; 476 477 if(fqn == null || key == null || value == null) return; 478 n=findParentNode(fqn, child_name, true); 479 if(child_name.getValue() != null) { 480 child_exists=n.childExists(child_name.getValue()); 481 n.createChild(child_name.getValue(), fqn, n, key, value); 482 } 483 else { 484 child_exists=true; 485 n.setData(key, value); 486 } 487 if(child_exists) 488 notifyNodeModified(fqn); 489 else 490 notifyNodeAdded(fqn); 491 } 492 493 494 public void _remove(String fqn) { 495 Node n; 496 StringHolder child_name=new StringHolder(); 497 498 if(fqn == null) return; 499 if(fqn.equals(SEPARATOR)) { 500 root.removeAll(); 501 notifyNodeRemoved(fqn); 502 return; 503 } 504 n=findParentNode(fqn, child_name, false); 505 if(n == null) return; 506 n.removeChild(child_name.getValue(), fqn); 507 notifyNodeRemoved(fqn); 508 } 509 510 511 public void _remove(String fqn, String key) { 512 Node n; 513 514 if(fqn == null || key == null) return; 515 n=findNode(fqn); 516 if(n != null) 517 n.removeData(key); 518 } 519 520 521 public void _removeData(String fqn) { 522 Node n; 523 524 if(fqn == null) return; 525 n=findNode(fqn); 526 if(n != null) 527 n.removeData(); 528 } 529 530 531 532 533 534 535 536 537 538 539 540 541 public void receive(Message msg) { 542 Request req=null; 543 544 if(msg == null || msg.getLength() == 0) 545 return; 546 try { 547 req=(Request)msg.getObject(); 548 request_queue.add(req); 549 } 550 catch(QueueClosedException queue_closed_ex) { 551 if(log.isErrorEnabled()) log.error("request queue is null"); 552 } 553 catch(Exception ex) { 554 if(log.isErrorEnabled()) log.error("failed unmarshalling request: " + ex); 555 return; 556 } 557 } 558 559 560 public byte[] getState() { 561 try { 562 return Util.objectToByteBuffer(root.clone()); 563 } 564 catch(Throwable ex) { 565 if(log.isErrorEnabled()) log.error("exception returning cache: " + ex); 566 return null; 567 } 568 } 569 570 571 public void setState(byte[] new_state) { 572 Node new_root=null; 573 Object obj; 574 575 if(new_state == null) { 576 if(log.isInfoEnabled()) log.info("new cache is null"); 577 return; 578 } 579 try { 580 obj=Util.objectFromByteBuffer(new_state); 581 new_root=(Node)((Node)obj).clone(); 582 root=new_root; 583 notifyAllNodesCreated(root); 584 } 585 catch(Throwable ex) { 586 if(log.isErrorEnabled()) log.error("could not set cache: " + ex); 587 } 588 } 589 590 591 592 593 594 595 596 597 598 public void viewAccepted(View new_view) { 599 Vector new_mbrs=new_view.getMembers(); 600 601 605 if(new_mbrs != null) { 606 notifyViewChange(new_view); 607 members.removeAllElements(); 608 for(int i=0; i < new_mbrs.size(); i++) 609 members.addElement(new_mbrs.elementAt(i)); 610 } 611 if(members.size() > 1) { 614 send_message=true; 615 } 616 else { 617 send_message=false; 618 } 619 } 620 621 622 623 public void suspect(Address suspected_mbr) { 624 ; 625 } 626 627 628 629 public void block() { 630 } 631 632 633 634 635 636 637 public void run() { 638 Request req; 639 String fqn=null; 640 641 while(request_handler != null) { 642 try { 643 req=(Request)request_queue.remove(0); 644 fqn=req.fqn; 645 switch(req.type) { 646 case Request.PUT: 647 if(req.key != null && req.value != null) 648 _put(fqn, req.key, req.value); 649 else 650 _put(fqn, req.data); 651 break; 652 case Request.REMOVE: 653 if(req.key != null) 654 _remove(fqn, req.key); 655 else 656 _remove(fqn); 657 break; 658 default: 659 if(log.isErrorEnabled()) log.error("type " + req.type + " unknown"); 660 break; 661 } 662 } 663 catch(QueueClosedException queue_closed_ex) { 664 request_handler=null; 665 break; 666 } 667 catch(Throwable other_ex) { 668 if(log.isWarnEnabled()) log.warn("exception processing request: " + other_ex); 669 } 670 } 671 } 672 673 674 683 Node findParentNode(String fqn, StringHolder child_name, boolean create_if_not_exists) { 684 Node curr=root, node; 685 StringTokenizer tok; 686 String name; 687 StringBuffer sb=null; 688 689 if(fqn == null || fqn.equals(SEPARATOR) || "".equals(fqn)) 690 return curr; 691 692 sb=new StringBuffer (); 693 tok=new StringTokenizer(fqn, SEPARATOR); 694 while(tok.countTokens() > 1) { 695 name=tok.nextToken(); 696 sb.append(SEPARATOR).append(name); 697 node=curr.getChild(name); 698 if(node == null && create_if_not_exists) 699 node=curr.createChild(name, sb.toString(), null, null); 700 if(node == null) 701 return null; 702 else 703 curr=node; 704 } 705 706 if(tok.countTokens() > 0 && child_name != null) 707 child_name.setValue(tok.nextToken()); 708 return curr; 709 } 710 711 712 718 Node findNode(String fqn) { 719 StringHolder sh=new StringHolder(); 720 Node n=findParentNode(fqn, sh, false); 721 String child_name=sh.getValue(); 722 723 if(fqn == null || fqn.equals(SEPARATOR) || "".equals(fqn)) 724 return root; 725 726 if(n == null || child_name == null) 727 return null; 728 else 729 return n.getChild(child_name); 730 } 731 732 733 void notifyNodeAdded(String fqn) { 734 for(int i=0; i < listeners.size(); i++) 735 ((ReplicatedTreeListener)listeners.elementAt(i)).nodeAdded(fqn); 736 } 737 738 void notifyNodeRemoved(String fqn) { 739 for(int i=0; i < listeners.size(); i++) 740 ((ReplicatedTreeListener)listeners.elementAt(i)).nodeRemoved(fqn); 741 } 742 743 void notifyNodeModified(String fqn) { 744 for(int i=0; i < listeners.size(); i++) 745 ((ReplicatedTreeListener)listeners.elementAt(i)).nodeModified(fqn); 746 } 747 748 void notifyViewChange(View v) { 749 for(int i=0; i < listeners.size(); i++) 750 ((ReplicatedTreeListener)listeners.elementAt(i)).viewChange(v); 751 } 752 753 755 void notifyAllNodesCreated(Node curr) { 756 Node n; 757 Map children; 758 759 if(curr == null) return; 760 notifyNodeAdded(curr.fqn); 761 if((children=curr.getChildren()) != null) { 762 for(Iterator it=children.values().iterator(); it.hasNext();) { 763 n=(Node)it.next(); 764 notifyAllNodesCreated(n); 765 } 766 } 767 } 768 769 770 public static class Node implements Serializable { 771 String name=null; String fqn=null; Node parent=null; TreeMap children=null; HashMap data=null; 778 779 private Node(String child_name, String fqn, Node parent, HashMap data) { 780 name=child_name; 781 this.fqn=fqn; 782 this.parent=parent; 783 if(data != null) this.data=(HashMap)data.clone(); 784 } 785 786 private Node(String child_name, String fqn, Node parent, String key, Object value) { 787 name=child_name; 788 this.fqn=fqn; 789 this.parent=parent; 790 if(data == null) data=new HashMap(); 791 data.put(key, value); 792 } 793 794 void setData(Map data) { 795 if(data == null) return; 796 if(this.data == null) 797 this.data=new HashMap(); 798 this.data.putAll(data); 799 } 800 801 void setData(String key, Object value) { 802 if(this.data == null) 803 this.data=new HashMap(); 804 this.data.put(key, value); 805 } 806 807 HashMap getData() { 808 return data; 809 } 810 811 Object getData(String key) { 812 return data != null? data.get(key) : null; 813 } 814 815 816 boolean childExists(String child_name) { 817 if(child_name == null) return false; 818 return children != null? children.containsKey(child_name) : false; 819 } 820 821 822 Node createChild(String child_name, String fqn, Node parent, HashMap data) { 823 Node child=null; 824 825 if(child_name == null) return null; 826 if(children == null) children=new TreeMap(); 827 child=(Node)children.get(child_name); 828 if(child != null) 829 child.setData(data); 830 else { 831 child=new Node(child_name, fqn, parent, data); 832 children.put(child_name, child); 833 } 834 return child; 835 } 836 837 Node createChild(String child_name, String fqn, Node parent, String key, Object value) { 838 Node child=null; 839 840 if(child_name == null) return null; 841 if(children == null) children=new TreeMap(); 842 child=(Node)children.get(child_name); 843 if(child != null) 844 child.setData(key, value); 845 else { 846 child=new Node(child_name, fqn, parent, key, value); 847 children.put(child_name, child); 848 } 849 return child; 850 } 851 852 853 Node getChild(String child_name) { 854 return child_name == null? null : children == null? null : (Node)children.get(child_name); 855 } 856 857 Map getChildren() { 858 return children; 859 } 860 861 void removeData(String key) { 862 if(data != null) 863 data.remove(key); 864 } 865 866 void removeData() { 867 if(data != null) 868 data.clear(); 869 } 870 871 void removeChild(String child_name, String fqn) { 872 if(child_name != null && children != null && children.containsKey(child_name)) { 873 children.remove(child_name); 874 } 875 } 876 877 void removeAll() { 878 if(children != null) 879 children.clear(); 880 } 881 882 void print(StringBuffer sb, int indent) { 883 printIndent(sb, indent); 884 sb.append(SEPARATOR).append(name); 885 if(children != null && children.size() > 0) { 886 Collection values=children.values(); 887 for(Iterator it=values.iterator(); it.hasNext();) { 888 sb.append('\n'); 889 ((Node)it.next()).print(sb, indent + INDENT); 890 } 891 } 892 } 893 894 void printIndent(StringBuffer sb, int indent) { 895 if(sb != null) { 896 for(int i=0; i < indent; i++) 897 sb.append(' '); 898 } 899 } 900 901 902 public String toString() { 903 StringBuffer sb=new StringBuffer (); 904 if(name != null) sb.append("\nname=" + name); 905 if(fqn != null) sb.append("\nfqn=" + fqn); 906 if(data != null) sb.append("\ndata=" + data); 907 return sb.toString(); 908 } 909 910 911 public Object clone() throws CloneNotSupportedException { 912 Node n=new Node(name, fqn, parent != null? (Node)parent.clone() : null, data); 913 if(children != null) n.children=(TreeMap)children.clone(); 914 return n; 915 } 916 917 } 918 919 920 private static class StringHolder { 921 String s=null; 922 923 private StringHolder() { 924 } 925 926 private StringHolder(String s) { 927 this.s=s; 928 } 929 930 void setValue(String s) { 931 this.s=s; 932 } 933 934 String getValue() { 935 return s; 936 } 937 } 938 939 940 943 private static class Request implements Serializable { 944 static final int PUT=1; 945 static final int REMOVE=2; 946 947 int type=0; 948 String fqn=null; 949 String key=null; 950 Object value=null; 951 HashMap data=null; 952 953 private Request(int type, String fqn) { 954 this.type=type; 955 this.fqn=fqn; 956 } 957 958 private Request(int type, String fqn, HashMap data) { 959 this(type, fqn); 960 this.data=data; 961 } 962 963 private Request(int type, String fqn, String key) { 964 this(type, fqn); 965 this.key=key; 966 } 967 968 private Request(int type, String fqn, String key, Object value) { 969 this(type, fqn); 970 this.key=key; 971 this.value=value; 972 } 973 974 public String toString() { 975 StringBuffer sb=new StringBuffer (); 976 sb.append(type2String(type)).append(" ("); 977 if(fqn != null) sb.append(" fqn=" + fqn); 978 switch(type) { 979 case PUT: 980 if(data != null) sb.append(", data=" + data); 981 if(key != null) sb.append(", key=" + key); 982 if(value != null) sb.append(", value=" + value); 983 break; 984 case REMOVE: 985 if(key != null) sb.append(", key=" + key); 986 break; 987 default: 988 break; 989 } 990 sb.append(')'); 991 return sb.toString(); 992 } 993 994 static String type2String(int t) { 995 switch(t) { 996 case PUT: 997 return "PUT"; 998 case REMOVE: 999 return "REMOVE"; 1000 default: 1001 return "UNKNOWN"; 1002 } 1003 } 1004 1005 } 1006 1007 1008 public static void main(String [] args) { 1009 1010 1011 ReplicatedTree tree=null; 1012 HashMap m=new HashMap(); 1013 String props; 1014 1015 props="UDP(mcast_addr=224.0.0.36;mcast_port=55566;ip_ttl=32;" + 1016 "mcast_send_buf_size=150000;mcast_recv_buf_size=80000):" + 1017 "PING(timeout=2000;num_initial_members=3):" + 1018 "MERGE2(min_interval=5000;max_interval=10000):" + 1019 "FD_SOCK:" + 1020 "VERIFY_SUSPECT(timeout=1500):" + 1021 "pbcast.STABLE(desired_avg_gossip=20000):" + 1022 "pbcast.NAKACK(gc_lag=50;retransmit_timeout=600,1200,2400,4800):" + 1023 "UNICAST(timeout=5000):" + 1024 "FRAG(frag_size=16000;down_thread=false;up_thread=false):" + 1025 "pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;" + 1026 "shun=false;print_local_addr=true):" + 1027 "pbcast.STATE_TRANSFER"; 1028 1030 try { 1031 1032 tree=new ReplicatedTree(null, props, 10000); 1033 tree.addReplicatedTreeListener(new MyListener()); 1035 tree.put("/a/b/c", null); 1036 tree.put("/a/b/c1", null); 1037 tree.put("/a/b/c2", null); 1038 tree.put("/a/b1/chat", null); 1039 tree.put("/a/b1/chat2", null); 1040 tree.put("/a/b1/chat5", null); 1041 System.out.println(tree); 1042 m.put("name", "Bela Ban"); 1043 m.put("age", new Integer (36)); 1044 m.put("cube", "240-17"); 1045 tree.put("/a/b/c", m); 1046 System.out.println("info for for \"/a/b/c\" is " + tree.print("/a/b/c")); 1047 tree.put("/a/b/c", "age", new Integer (37)); 1048 System.out.println("info for for \"/a/b/c\" is " + tree.print("/a/b/c")); 1049 tree.remove("/a/b"); 1050 System.out.println(tree); 1051 } 1052 catch(Exception ex) { 1053 System.err.println(ex); 1054 } 1055 } 1056 1057 1058 static class MyListener implements ReplicatedTreeListener { 1059 1060 public void nodeAdded(String fqn) { 1061 System.out.println("** node added: " + fqn); 1062 } 1063 1064 public void nodeRemoved(String fqn) { 1065 System.out.println("** node removed: " + fqn); 1066 } 1067 1068 public void nodeModified(String fqn) { 1069 System.out.println("** node modified: " + fqn); 1070 } 1071 1072 public void viewChange(View new_view) { 1073 System.out.println("** view change: " + new_view); 1074 } 1075 1076 } 1077 1078 1079} 1080 1081 1082 | Popular Tags |