001/** 002 * 003 * Copyright (c) 2013 University of Le Havre 004 * 005 * @file NetStreamDecoder.java 006 * @date May 31, 2013 007 * 008 * @author Yoann Pigné 009 * 010 */ 011package org.graphstream.stream.netstream; 012 013import java.io.IOException; 014import java.io.InputStream; 015import java.nio.ByteBuffer; 016import java.nio.charset.Charset; 017import java.util.HashMap; 018 019import org.graphstream.stream.thread.ThreadProxyPipe; 020 021/** 022 * 023 */ 024public class DefaultNetStreamDecoder implements NetStreamDecoder { 025 026 /** 027 * Show debugging messages. 028 */ 029 protected boolean debug = true; 030 031 /** 032 * The current pipe commands are being written to. 033 */ 034 protected ThreadProxyPipe currentStream; 035 036 /** 037 * Pairs (key,value) where the key is the listener ID and the value the MBox 038 * of the listener. This can be modified by other threads and must be 039 * properly locked. 040 * 041 * @see #register(String,ThreadProxyPipe) 042 */ 043 // protected HashMap<String,MBox> boxes = new HashMap<String,MBox>(); 044 protected HashMap<String, ThreadProxyPipe> streams = new HashMap<String, ThreadProxyPipe>(); 045 046 /* (non-Javadoc) 047 * @see org.graphstream.stream.netstream.NetStreamDecoder#getStream(java.lang.String) 048 */ 049 public synchronized ThreadProxyPipe getStream(String name) { 050 ThreadProxyPipe s = streams.get(name); 051 if (s == null) { 052 s = new ThreadProxyPipe(); 053 streams.put(name, s); 054 } 055 return s; 056 } 057 /* (non-Javadoc) 058 * @see org.graphstream.stream.netstream.NetStreamDecoder#getDefaultStream() 059 */ 060 061 public synchronized ThreadProxyPipe getDefaultStream() { 062 ThreadProxyPipe s = streams.get("default"); 063 if (s == null) { 064 s = new ThreadProxyPipe(); 065 streams.put("default", s); 066 } 067 return s; 068 069 } 070 /* (non-Javadoc) 071 * @see org.graphstream.stream.netstream.NetStreamDecoder#register(java.lang.String, org.graphstream.stream.thread.ThreadProxyPipe) 072 */ 073 public synchronized void register(String name, ThreadProxyPipe stream) 074 throws Exception { 075 if (streams.containsKey(name)) 076 throw new Exception("name " + name + " already registered"); 077 078 streams.put(name, stream); 079 080 if (debug) 081 debug("registered pipe %s", name); 082 } 083 084 /* (non-Javadoc) 085 * @see org.graphstream.stream.netstream.NetStreamDecoder#decodeMessage(java.io.InputStream) 086 */ 087 public void decodeMessage(InputStream in) throws IOException { 088 089 int cmd = 0; 090 091 // First read the name of the stream that will be addressed. 092 String stream = readString(in); 093 if (debug) { 094 debug("Stream \"%s\" is addressed in this message.", stream); 095 } 096 currentStream = getStream(stream); 097 098 cmd = in.read(); 099 if (cmd != -1) { 100 if (cmd == NetStreamConstants.EVENT_ADD_NODE) { 101 serve_EVENT_ADD_NODE(in); 102 } else if ((cmd & 0xFF) == (NetStreamConstants.EVENT_DEL_NODE & 0xFF)) { 103 serve_DEL_NODE(in); 104 } else if (cmd == NetStreamConstants.EVENT_ADD_EDGE) { 105 serve_EVENT_ADD_EDGE(in); 106 } else if (NetStreamConstants.EVENT_DEL_EDGE == cmd) { 107 serve_EVENT_DEL_EDGE(in); 108 } else if (cmd == NetStreamConstants.EVENT_STEP) { 109 serve_EVENT_STEP(in); 110 } else if (cmd == NetStreamConstants.EVENT_CLEARED) { 111 serve_EVENT_CLEARED(in); 112 } else if (cmd == NetStreamConstants.EVENT_ADD_GRAPH_ATTR) { 113 serve_EVENT_ADD_GRAPH_ATTR(in); 114 } else if (cmd == NetStreamConstants.EVENT_CHG_GRAPH_ATTR) { 115 serve_EVENT_CHG_GRAPH_ATTR(in); 116 } else if (cmd == NetStreamConstants.EVENT_DEL_GRAPH_ATTR) { 117 serve_EVENT_DEL_GRAPH_ATTR(in); 118 } else if (cmd == NetStreamConstants.EVENT_ADD_NODE_ATTR) { 119 serve_EVENT_ADD_NODE_ATTR(in); 120 } else if (cmd == NetStreamConstants.EVENT_CHG_NODE_ATTR) { 121 serve_EVENT_CHG_NODE_ATTR(in); 122 } else if (cmd == NetStreamConstants.EVENT_DEL_NODE_ATTR) { 123 serve_EVENT_DEL_NODE_ATTR(in); 124 } else if (cmd == NetStreamConstants.EVENT_ADD_EDGE_ATTR) { 125 serve_EVENT_ADD_EDGE_ATTR(in); 126 } else if (cmd == NetStreamConstants.EVENT_CHG_EDGE_ATTR) { 127 serve_EVENT_CHG_EDGE_ATTR(in); 128 } else if (cmd == NetStreamConstants.EVENT_DEL_EDGE_ATTR) { 129 serve_EVENT_DEL_EDGE_ATTR(in); 130 } else if (cmd == NetStreamConstants.EVENT_END) { 131 debug("NetStreamReceiver : Client properly ended the connection."); 132 return; 133 } else { 134 debug("NetStreamReceiver: Don't know this command: " + cmd); 135 return; 136 } 137 cmd = in.read(); 138 } 139 } 140 141 142 143 /** 144 * @param in 145 * @see NetStreamConstants.EVENT_DEL_EDGE 146 */ 147 protected void serve_EVENT_DEL_EDGE_ATTR(InputStream in) { 148 if (debug) { 149 debug("NetStreamServer: Received DEL_EDGE_ATTR command."); 150 } 151 String sourceId = readString(in); 152 long timeId = readUnsignedVarint(in); 153 String edgeId = readString(in); 154 String attrId = readString(in); 155 currentStream.edgeAttributeRemoved(sourceId, timeId, edgeId, attrId); 156 } 157 158 /** 159 * @see NetStreamConstants.EVENT_CHG_EDGE_ATTR 160 */ 161 protected void serve_EVENT_CHG_EDGE_ATTR(InputStream in) { 162 if (debug) { 163 debug("NetStreamServer: Received CHG_EDGE_ATTR command."); 164 } 165 String sourceId = readString(in); 166 long timeId = readUnsignedVarint(in); 167 String edgeId = readString(in); 168 String attrId = readString(in); 169 int oldValueType = readType(in); 170 Object oldValue = readValue(in, oldValueType); 171 int newValueType = readType(in); 172 Object newValue = readValue(in, newValueType); 173 174 currentStream.edgeAttributeChanged(sourceId, timeId, edgeId, attrId, 175 oldValue, newValue); 176 177 } 178 179 /** 180 * @see NetStreamConstants.EVENT_ADD_EDGE_ATTR 181 */ 182 protected void serve_EVENT_ADD_EDGE_ATTR(InputStream in) { 183 if (debug) { 184 debug("NetStreamServer: Received ADD_EDGE_ATTR command."); 185 } 186 String sourceId = readString(in); 187 long timeId = readUnsignedVarint(in); 188 String edgeId = readString(in); 189 String attrId = readString(in); 190 Object value = readValue(in, readType(in)); 191 192 currentStream.edgeAttributeAdded(sourceId, timeId, edgeId, attrId, 193 value); 194 195 } 196 197 /** 198 * @see NetStreamConstants.EVENT_DEL_NODE_ATTR 199 */ 200 protected void serve_EVENT_DEL_NODE_ATTR(InputStream in) { 201 if (debug) { 202 debug("NetStreamServer: Received DEL_NODE_ATTR command."); 203 } 204 String sourceId = readString(in); 205 long timeId = readUnsignedVarint(in); 206 String nodeId = readString(in); 207 String attrId = readString(in); 208 209 currentStream.nodeAttributeRemoved(sourceId, timeId, nodeId, attrId); 210 211 } 212 213 /** 214 * @see NetStreamConstants.EVENT_CHG_NODE_ATTR 215 */ 216 protected void serve_EVENT_CHG_NODE_ATTR(InputStream in) { 217 if (debug) { 218 debug("NetStreamServer: Received EVENT_CHG_NODE_ATTR command."); 219 } 220 String sourceId = readString(in); 221 long timeId = readUnsignedVarint(in); 222 String nodeId = readString(in); 223 String attrId = readString(in); 224 int oldValueType = readType(in); 225 Object oldValue = readValue(in, oldValueType); 226 int newValueType = readType(in); 227 Object newValue = readValue(in, newValueType); 228 229 currentStream.nodeAttributeChanged(sourceId, timeId, nodeId, attrId, 230 oldValue, newValue); 231 } 232 233 /** 234 * @see NetStreamConstants.EVENT_ADD_NODE_ATTR 235 */ 236 protected void serve_EVENT_ADD_NODE_ATTR(InputStream in) { 237 if (debug) { 238 debug("NetStreamServer: Received EVENT_ADD_NODE_ATTR command."); 239 } 240 String sourceId = readString(in); 241 long timeId = readUnsignedVarint(in); 242 String nodeId = readString(in); 243 String attrId = readString(in); 244 Object value = readValue(in, readType(in)); 245 246 currentStream.nodeAttributeAdded(sourceId, timeId, nodeId, attrId, 247 value); 248 } 249 250 /** 251 * @see NetStreamConstants.EVENT_DEL_GRAPH_ATTR 252 */ 253 protected void serve_EVENT_DEL_GRAPH_ATTR(InputStream in) { 254 if (debug) { 255 debug("NetStreamServer: Received EVENT_DEL_GRAPH_ATTR command."); 256 } 257 String sourceId = readString(in); 258 long timeId = readUnsignedVarint(in); 259 String attrId = readString(in); 260 261 currentStream.graphAttributeRemoved(sourceId, timeId, attrId); 262 } 263 264 /** 265 * @see NetStreamConstants.EVENT_CHG_GRAPH_ATTR 266 */ 267 protected void serve_EVENT_CHG_GRAPH_ATTR(InputStream in) { 268 if (debug) { 269 debug("NetStreamServer: Received EVENT_CHG_GRAPH_ATTR command."); 270 } 271 String sourceId = readString(in); 272 long timeId = readUnsignedVarint(in); 273 String attrId = readString(in); 274 int oldValueType = readType(in); 275 Object oldValue = readValue(in, oldValueType); 276 int newValueType = readType(in); 277 Object newValue = readValue(in, newValueType); 278 279 currentStream.graphAttributeChanged(sourceId, timeId, attrId, oldValue, 280 newValue); 281 282 } 283 284 /** 285 * @see NetStreamConstants.EVENT_ADD_GRAPH_ATTR 286 */ 287 protected void serve_EVENT_ADD_GRAPH_ATTR(InputStream in) { 288 if (debug) { 289 debug("NetStreamServer: Received EVENT_ADD_GRAPH_ATTR command."); 290 } 291 String sourceId = readString(in); 292 long timeId = readUnsignedVarint(in); 293 String attrId = readString(in); 294 Object value = readValue(in, readType(in)); 295 if (debug) { 296 debug("NetStreamServer | EVENT_ADD_GRAPH_ATTR | %s=%s", attrId, 297 value.toString()); 298 } 299 currentStream.graphAttributeAdded(sourceId, timeId, attrId, value); 300 301 } 302 303 /** 304 * @see NetStreamConstants.EVENT_CLEARED 305 */ 306 protected void serve_EVENT_CLEARED(InputStream in) { 307 if (debug) { 308 debug("NetStreamServer: Received EVENT_CLEARED command."); 309 } 310 String sourceId = readString(in); 311 long timeId = readUnsignedVarint(in); 312 currentStream.graphCleared(sourceId, timeId); 313 314 } 315 316 /** 317 * @see NetStreamConstants.EVENT_STEP 318 */ 319 protected void serve_EVENT_STEP(InputStream in) { 320 if (debug) { 321 debug("NetStreamServer: Received EVENT_STEP command."); 322 } 323 String sourceId = readString(in); 324 long timeId = readUnsignedVarint(in); 325 double time = readDouble(in); 326 currentStream.stepBegins(sourceId, timeId, time); 327 } 328 329 /** 330 * @see NetStreamConstants.EVENT_DEL_EDGE 331 */ 332 protected void serve_EVENT_DEL_EDGE(InputStream in) { 333 if (debug) { 334 debug("NetStreamServer: Received EVENT_DEL_EDGE command."); 335 } 336 String sourceId = readString(in); 337 long timeId = readUnsignedVarint(in); 338 String edgeId = readString(in); 339 currentStream.edgeRemoved(sourceId, timeId, edgeId); 340 } 341 342 /** 343 * @see NetStreamConstants.EVENT_ADD_EDGE 344 */ 345 protected void serve_EVENT_ADD_EDGE(InputStream in) { 346 if (debug) { 347 debug("NetStreamServer: Received ADD_EDGE command."); 348 } 349 String sourceId = readString(in); 350 long timeId = readUnsignedVarint(in); 351 String edgeId = readString(in); 352 String from = readString(in); 353 String to = readString(in); 354 boolean directed = readBoolean(in); 355 currentStream.edgeAdded(sourceId, timeId, edgeId, from, to, directed); 356 } 357 358 /** 359 * @see NetStreamConstants.DEL_NODE 360 */ 361 protected void serve_DEL_NODE(InputStream in) { 362 if (debug) { 363 debug("NetStreamServer: Received DEL_NODE command."); 364 } 365 String sourceId = readString(in); 366 long timeId = readUnsignedVarint(in); 367 String nodeId = readString(in); 368 currentStream.nodeRemoved(sourceId, timeId, nodeId); 369 } 370 371 /** 372 * @see NetStreamConstants.EVENT_ADD_NODE 373 */ 374 protected void serve_EVENT_ADD_NODE(InputStream in) { 375 if (debug) { 376 debug("NetStreamServer: Received EVENT_ADD_NODE command"); 377 } 378 String sourceId = readString(in); 379 long timeId = readUnsignedVarint(in); 380 String nodeId = readString(in); 381 currentStream.nodeAdded(sourceId, timeId, nodeId); 382 383 } 384 385 /** 386 * @param in 387 * @return 388 */ 389 protected int readType(InputStream in) { 390 try { 391 int data = 0; 392 if ((data = in.read()) == -1) { 393 debug("readType : could not read type"); 394 return 0; 395 } 396 if (debug) { 397 debug("NetStreamServer: type "+data); 398 } 399 return data; 400 } catch (IOException e) { 401 debug("readType: could not read type"); 402 e.printStackTrace(); 403 } 404 405 return 0; 406 } 407 408 protected Object readValue(InputStream in, int valueType) { 409 if (NetStreamConstants.TYPE_BOOLEAN == valueType) { 410 return readBoolean(in); 411 } else if (NetStreamConstants.TYPE_BOOLEAN_ARRAY == valueType) { 412 return readBooleanArray(in); 413 } else if (NetStreamConstants.TYPE_BYTE == valueType) { 414 return readByte(in); 415 } else if (NetStreamConstants.TYPE_BYTE_ARRAY == valueType) { 416 return readByteArray(in); 417 } else if (NetStreamConstants.TYPE_SHORT == valueType) { 418 return readShort(in); 419 } else if (NetStreamConstants.TYPE_SHORT_ARRAY == valueType) { 420 return readShortArray(in); 421 } else if (NetStreamConstants.TYPE_INT == valueType) { 422 return readInt(in); 423 } else if (NetStreamConstants.TYPE_INT_ARRAY == valueType) { 424 return readIntArray(in); 425 } else if (NetStreamConstants.TYPE_LONG == valueType) { 426 return readLong(in); 427 } else if (NetStreamConstants.TYPE_LONG_ARRAY == valueType) { 428 return readLongArray(in); 429 } else if (NetStreamConstants.TYPE_FLOAT == valueType) { 430 return readFloat(in); 431 } else if (NetStreamConstants.TYPE_FLOAT_ARRAY == valueType) { 432 return readFloatArray(in); 433 } else if (NetStreamConstants.TYPE_DOUBLE == valueType) { 434 return readDouble(in); 435 } else if (NetStreamConstants.TYPE_DOUBLE_ARRAY == valueType) { 436 return readDoubleArray(in); 437 } else if (NetStreamConstants.TYPE_STRING == valueType) { 438 return readString(in); 439 } else if (NetStreamConstants.TYPE_ARRAY == valueType) { 440 return readArray(in); 441 } 442 return null; 443 } 444 445 /** 446 * @param in 447 * @return 448 */ 449 protected Object[] readArray(InputStream in) { 450 451 int len = (int) readUnsignedVarint(in); 452 453 Object[] array = new Object[len]; 454 for (int i = 0; i < len; i++) { 455 array[i] = readValue(in, readType(in)); 456 } 457 return array; 458 459 } 460 461 protected String readString(InputStream in) { 462 try { 463 464 int len = (int) readUnsignedVarint(in); 465 byte[] data = new byte[len]; 466 if (in.read(data, 0, len) != len) { 467 return null; 468 } 469 String s = new String(data, Charset.forName("UTF-8")); 470 return s; 471 } catch (IOException e) { 472 debug("readString: could not read string"); 473 e.printStackTrace(); 474 } 475 return null; 476 } 477 478 protected Boolean readBoolean(InputStream in) { 479 int data = 0; 480 try { 481 data = in.read(); 482 } catch (IOException e) { 483 debug("readByte: could not read"); 484 e.printStackTrace(); 485 } 486 return data == 0 ? false : true; 487 } 488 489 protected Byte readByte(InputStream in) { 490 byte data = 0; 491 try { 492 data = (byte) in.read(); 493 } catch (IOException e) { 494 debug("readByte: could not read"); 495 e.printStackTrace(); 496 } 497 return data; 498 } 499 500 protected long readUnsignedVarint(InputStream in) { 501 try { 502 503 int size = 0; 504 long[] data = new long[9]; 505 do { 506 data[size] = in.read(); 507 508 size++; 509 510 //int bt =data[size-1]; 511 //if (bt < 0) bt = (bt & 127) + (bt & 128); 512 //System.out.println("test "+bt+" -> "+(data[size - 1]& 128) ); 513 } while ((data[size - 1] & 128) == 128); 514 long number = 0; 515 for (int i = 0; i < size; i++) { 516 517 number ^= (data[i] & 127L) << (i * 7L); 518 519 } 520 521 return number; 522 523 } catch (IOException e) { 524 debug("readUnsignedVarintFromInteger: could not read"); 525 e.printStackTrace(); 526 } 527 return 0L; 528 } 529 530 531 532 protected long readVarint(InputStream in) { 533 long number = readUnsignedVarint(in); 534 return ((number & 1) == 0) ? number >> 1 : -(number >> 1); 535 } 536 537 protected Short readShort(InputStream in) { 538 return (short) readVarint(in); 539 } 540 541 protected Integer readInt(InputStream in) { 542 return (int) readVarint(in); 543 } 544 545 protected Long readLong(InputStream in) { 546 return readVarint(in); 547 } 548 549 protected Float readFloat(InputStream in) { 550 byte[] data = new byte[4]; 551 try { 552 if (in.read(data, 0, 4) != 4) { 553 debug("readFloat: could not read"); 554 return 0f; 555 } 556 } catch (IOException e) { 557 debug("readFloat: could not read"); 558 e.printStackTrace(); 559 } 560 ByteBuffer bb = ByteBuffer.allocate(4); 561 bb.put(data); 562 bb.flip(); 563 return bb.getFloat(); 564 } 565 566 protected Double readDouble(InputStream in) { 567 byte[] data = new byte[8]; 568 try { 569 if (in.read(data, 0, 8) != 8) { 570 debug("readDouble: could not read"); 571 return 0.0; 572 } 573 } catch (IOException e) { 574 debug("readDouble: could not read"); 575 e.printStackTrace(); 576 } 577 ByteBuffer bb = ByteBuffer.allocate(8); 578 bb.put(data); 579 bb.flip(); 580 return bb.getDouble(); 581 } 582 583 /** 584 * @param in 585 * @return 586 * @throws IOException 587 */ 588 protected Integer[] readIntArray(InputStream in) { 589 590 int len = (int) readUnsignedVarint(in); 591 592 Integer[] res = new Integer[len]; 593 for (int i = 0; i < len; i++) { 594 res[i] = (int) readVarint(in); 595 //System.out.printf("array[%d]=%d%n",i,res[i]); 596 } 597 return res; 598 } 599 600 protected Boolean[] readBooleanArray(InputStream in) { 601 byte[] data = null; 602 603 try { 604 int len = (int) readUnsignedVarint(in); 605 606 data = new byte[len]; 607 if (in.read(data, 0, len) != len) { 608 debug("readBooleanArray: could not read array"); 609 return null; 610 } 611 612 ByteBuffer bb = ByteBuffer.allocate(len); 613 bb.put(data); 614 bb.flip(); 615 Boolean[] res = new Boolean[len]; 616 for (int i = 0; i < len; i++) { 617 618 byte b = bb.get(); 619 620 res[i] = b == 0 ? false : true; 621 } 622 return res; 623 } catch (IOException e) { 624 debug("readBooleanArray: could not read array"); 625 e.printStackTrace(); 626 } 627 return null; 628 } 629 630 protected Byte[] readByteArray(InputStream in) { 631 byte[] data = null; 632 633 try { 634 int len = (int) readUnsignedVarint(in); 635 636 data = new byte[len]; 637 if (in.read(data, 0, len) != len) { 638 debug("readByteArray: could not read array"); 639 return null; 640 } 641 642 ByteBuffer bb = ByteBuffer.allocate(len); 643 bb.put(data); 644 bb.flip(); 645 Byte[] res = new Byte[len]; 646 for (int i = 0; i < len; i++) { 647 648 res[i] = bb.get(); 649 650 } 651 return res; 652 } catch (IOException e) { 653 debug("readBooleanArray: could not read array"); 654 e.printStackTrace(); 655 } 656 return null; 657 } 658 659 protected Double[] readDoubleArray(InputStream in) { 660 byte[] data = null; 661 662 try { 663 int len = (int) readUnsignedVarint(in); 664 665 data = new byte[len * 8]; 666 if (in.read(data, 0, len * 8) != len * 8) { 667 debug("readDoubleArray: could not read array"); 668 return null; 669 } 670 671 ByteBuffer bb = ByteBuffer.allocate(8 * len); 672 bb.put(data); 673 bb.flip(); 674 Double[] res = new Double[len]; 675 for (int i = 0; i < len; i++) { 676 677 res[i] = bb.getDouble(); 678 } 679 return res; 680 } catch (IOException e) { 681 debug("readDoubleArray: could not read array"); 682 e.printStackTrace(); 683 } 684 return null; 685 } 686 687 protected Float[] readFloatArray(InputStream in) { 688 byte[] data = null; 689 690 try { 691 int len = (int) readUnsignedVarint(in); 692 693 data = new byte[len * 4]; 694 if (in.read(data, 0, len * 4) != len * 4) { 695 debug("readFloatArray: could not read array"); 696 return null; 697 } 698 699 ByteBuffer bb = ByteBuffer.allocate(4 * len); 700 bb.put(data); 701 bb.flip(); 702 Float[] res = new Float[len]; 703 for (int i = 0; i < len; i++) { 704 705 res[i] = bb.getFloat(); 706 } 707 return res; 708 } catch (IOException e) { 709 debug("readFloatArray: could not read array"); 710 e.printStackTrace(); 711 } 712 return null; 713 } 714 715 protected Long[] readLongArray(InputStream in) { 716 int len = (int) readUnsignedVarint(in); 717 718 Long[] res = new Long[len]; 719 for (int i = 0; i < len; i++) { 720 res[i] = readVarint(in); 721 } 722 return res; 723 } 724 725 protected Short[] readShortArray(InputStream in) { 726 int len = (int) readUnsignedVarint(in); 727 728 Short[] res = new Short[len]; 729 for (int i = 0; i < len; i++) { 730 res[i] = (short) readVarint(in); 731 //System.out.printf("array[%d]=%d%n",i,res[i]); 732 } 733 return res; 734 } 735 736 737 738 739 protected void debug(String message, Object... data) { 740 // System.err.print( LIGHT_YELLOW ); 741 System.err.printf("[//NetStreamDecoder | "); 742 // System.err.print( RESET ); 743 System.err.printf(message, data); 744 // System.err.print( LIGHT_YELLOW ); 745 System.err.printf("]%n"); 746 // System.err.println( RESET ); 747 } 748 749 /* (non-Javadoc) 750 * @see org.graphstream.stream.netstream.NetStreamDecoder#setDebugOn(boolean) 751 */ 752 public void setDebugOn(boolean on) { 753 debug = on; 754 } 755 756}