001package org.apache.maven.doxia.module.apt; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.PrintWriter; 023import java.io.Writer; 024import java.util.Stack; 025 026import org.apache.maven.doxia.sink.SinkEventAttributes; 027import org.apache.maven.doxia.sink.impl.AbstractTextSink; 028import org.codehaus.plexus.util.StringUtils; 029 030/** 031 * APT generator implementation. 032 * <br/> 033 * <b>Note</b>: The encoding used is UTF-8. 034 * 035 * @author eredmond 036 * @version $Id$ 037 * @since 1.0 038 */ 039public class AptSink 040 extends AbstractTextSink 041 implements AptMarkup 042{ 043 // ---------------------------------------------------------------------- 044 // Instance fields 045 // ---------------------------------------------------------------------- 046 047 /** A buffer that holds the current text when headerFlag or bufferFlag set to <code>true</code>. */ 048 private StringBuffer buffer; 049 050 /** A buffer that holds the table caption. */ 051 private StringBuilder tableCaptionBuffer; 052 053 /** author. */ 054 private String author; 055 056 /** title. */ 057 private String title; 058 059 /** date. */ 060 private String date; 061 062 /** startFlag. */ 063 private boolean startFlag; 064 065 /** tableCaptionFlag. */ 066 private boolean tableCaptionFlag; 067 068 /** headerFlag. */ 069 private boolean headerFlag; 070 071 /** bufferFlag. */ 072 private boolean bufferFlag; 073 074 /** itemFlag. */ 075 private boolean itemFlag; 076 077 /** verbatimFlag. */ 078 private boolean verbatimFlag; 079 080 /** boxed verbatim. */ 081 private boolean isBoxed; 082 083 /** gridFlag for tables. */ 084 private boolean gridFlag; 085 086 /** number of cells in a table. */ 087 private int cellCount; 088 089 /** The writer to use. */ 090 private final PrintWriter writer; 091 092 /** justification of table cells. */ 093 private int cellJustif[]; 094 095 /** a line of a row in a table. */ 096 private String rowLine; 097 098 /** listNestingIndent. */ 099 private String listNestingIndent; 100 101 /** listStyles. */ 102 private final Stack<String> listStyles; 103 104 // ---------------------------------------------------------------------- 105 // Public protected methods 106 // ---------------------------------------------------------------------- 107 108 /** 109 * Constructor, initialize the Writer and the variables. 110 * 111 * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer. 112 * You could use <code>newWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}. 113 */ 114 protected AptSink( Writer writer ) 115 { 116 this.writer = new PrintWriter( writer ); 117 this.listStyles = new Stack<String>(); 118 119 init(); 120 } 121 122 /** 123 * Returns the buffer that holds the current text. 124 * 125 * @return A StringBuffer. 126 */ 127 protected StringBuffer getBuffer() 128 { 129 return buffer; 130 } 131 132 /** 133 * Used to determine whether we are in head mode. 134 * 135 * @param headFlag True for head mode. 136 */ 137 protected void setHeadFlag( boolean headFlag ) 138 { 139 this.headerFlag = headFlag; 140 } 141 142 /** 143 * Reset all variables. 144 * 145 * @deprecated since 1.1.2, use {@link #init()} instead of. 146 */ 147 protected void resetState() 148 { 149 init(); 150 } 151 152 /** {@inheritDoc} */ 153 protected void init() 154 { 155 super.init(); 156 157 resetBuffer(); 158 159 this.tableCaptionBuffer = new StringBuilder(); 160 this.listNestingIndent = ""; 161 162 this.author = null; 163 this.title = null; 164 this.date = null; 165 this.startFlag = true; 166 this.tableCaptionFlag = false; 167 this.headerFlag = false; 168 this.bufferFlag = false; 169 this.itemFlag = false; 170 this.verbatimFlag = false; 171 this.isBoxed = false; 172 this.gridFlag = false; 173 this.cellCount = 0; 174 this.cellJustif = null; 175 this.rowLine = null; 176 this.listStyles.clear(); 177 } 178 179 /** 180 * Reset the StringBuilder. 181 */ 182 protected void resetBuffer() 183 { 184 buffer = new StringBuffer(); 185 } 186 187 /** 188 * Reset the TableCaptionBuffer. 189 */ 190 protected void resetTableCaptionBuffer() 191 { 192 tableCaptionBuffer = new StringBuilder(); 193 } 194 195 /** {@inheritDoc} */ 196 public void head() 197 { 198 boolean startFlag = this.startFlag; 199 200 init(); 201 202 headerFlag = true; 203 this.startFlag = startFlag; 204 } 205 206 /** {@inheritDoc} */ 207 public void head_() 208 { 209 headerFlag = false; 210 211 if ( ! startFlag ) 212 { 213 write( EOL ); 214 } 215 write( HEADER_START_MARKUP + EOL ); 216 if ( title != null ) 217 { 218 write( " " + title + EOL ); 219 } 220 write( HEADER_START_MARKUP + EOL ); 221 if ( author != null ) 222 { 223 write( " " + author + EOL ); 224 } 225 write( HEADER_START_MARKUP + EOL ); 226 if ( date != null ) 227 { 228 write( " " + date + EOL ); 229 } 230 write( HEADER_START_MARKUP + EOL ); 231 } 232 233 /** {@inheritDoc} */ 234 public void title_() 235 { 236 if ( buffer.length() > 0 ) 237 { 238 title = buffer.toString(); 239 resetBuffer(); 240 } 241 } 242 243 /** {@inheritDoc} */ 244 public void author_() 245 { 246 if ( buffer.length() > 0 ) 247 { 248 author = buffer.toString(); 249 resetBuffer(); 250 } 251 } 252 253 /** {@inheritDoc} */ 254 public void date_() 255 { 256 if ( buffer.length() > 0 ) 257 { 258 date = buffer.toString(); 259 resetBuffer(); 260 } 261 } 262 263 /** {@inheritDoc} */ 264 public void section1_() 265 { 266 write( EOL ); 267 } 268 269 /** {@inheritDoc} */ 270 public void section2_() 271 { 272 write( EOL ); 273 } 274 275 /** {@inheritDoc} */ 276 public void section3_() 277 { 278 write( EOL ); 279 } 280 281 /** {@inheritDoc} */ 282 public void section4_() 283 { 284 write( EOL ); 285 } 286 287 /** {@inheritDoc} */ 288 public void section5_() 289 { 290 write( EOL ); 291 } 292 293 /** {@inheritDoc} */ 294 public void sectionTitle1() 295 { 296 write( EOL ); 297 } 298 299 /** {@inheritDoc} */ 300 public void sectionTitle1_() 301 { 302 write( EOL + EOL ); 303 } 304 305 /** {@inheritDoc} */ 306 public void sectionTitle2() 307 { 308 write( EOL + SECTION_TITLE_START_MARKUP ); 309 } 310 311 /** {@inheritDoc} */ 312 public void sectionTitle2_() 313 { 314 write( EOL + EOL ); 315 } 316 317 /** {@inheritDoc} */ 318 public void sectionTitle3() 319 { 320 write( EOL + StringUtils.repeat( SECTION_TITLE_START_MARKUP, 2 ) ); 321 } 322 323 /** {@inheritDoc} */ 324 public void sectionTitle3_() 325 { 326 write( EOL + EOL ); 327 } 328 329 /** {@inheritDoc} */ 330 public void sectionTitle4() 331 { 332 write( EOL + StringUtils.repeat( SECTION_TITLE_START_MARKUP, 3 ) ); 333 } 334 335 /** {@inheritDoc} */ 336 public void sectionTitle4_() 337 { 338 write( EOL + EOL ); 339 } 340 341 /** {@inheritDoc} */ 342 public void sectionTitle5() 343 { 344 write( EOL + StringUtils.repeat( SECTION_TITLE_START_MARKUP, 4 ) ); 345 } 346 347 /** {@inheritDoc} */ 348 public void sectionTitle5_() 349 { 350 write( EOL + EOL ); 351 } 352 353 /** {@inheritDoc} */ 354 public void list() 355 { 356 listNestingIndent += " "; 357 listStyles.push( LIST_START_MARKUP ); 358 write( EOL ); 359 } 360 361 /** {@inheritDoc} */ 362 public void list_() 363 { 364 if ( listNestingIndent.length() <= 1 ) 365 { 366 write( EOL + listNestingIndent + LIST_END_MARKUP + EOL ); 367 } 368 else 369 { 370 write( EOL ); 371 } 372 listNestingIndent = StringUtils.chomp( listNestingIndent, " " ); 373 listStyles.pop(); 374 itemFlag = false; 375 } 376 377 /** {@inheritDoc} */ 378 public void listItem() 379 { 380 //if ( !numberedList ) 381 //write( EOL + listNestingIndent + "*" ); 382 //else 383 numberedListItem(); 384 itemFlag = true; 385 } 386 387 /** {@inheritDoc} */ 388 public void listItem_() 389 { 390 write( EOL ); 391 itemFlag = false; 392 } 393 394 /** {@inheritDoc} */ 395 public void numberedList( int numbering ) 396 { 397 listNestingIndent += " "; 398 write( EOL ); 399 400 String style; 401 switch ( numbering ) 402 { 403 case NUMBERING_UPPER_ALPHA: 404 style = String.valueOf( NUMBERING_UPPER_ALPHA_CHAR ); 405 break; 406 case NUMBERING_LOWER_ALPHA: 407 style = String.valueOf( NUMBERING_LOWER_ALPHA_CHAR ); 408 break; 409 case NUMBERING_UPPER_ROMAN: 410 style = String.valueOf( NUMBERING_UPPER_ROMAN_CHAR ); 411 break; 412 case NUMBERING_LOWER_ROMAN: 413 style = String.valueOf( NUMBERING_LOWER_ROMAN_CHAR ); 414 break; 415 case NUMBERING_DECIMAL: 416 default: 417 style = String.valueOf( NUMBERING ); 418 } 419 420 listStyles.push( style ); 421 } 422 423 /** {@inheritDoc} */ 424 public void numberedList_() 425 { 426 if ( listNestingIndent.length() <= 1 ) 427 { 428 write( EOL + listNestingIndent + LIST_END_MARKUP + EOL ); 429 } 430 else 431 { 432 write( EOL ); 433 } 434 listNestingIndent = StringUtils.chomp( listNestingIndent, " " ); 435 listStyles.pop(); 436 itemFlag = false; 437 } 438 439 /** {@inheritDoc} */ 440 public void numberedListItem() 441 { 442 String style = listStyles.peek(); 443 if ( style.equals( String.valueOf( STAR ) ) ) 444 { 445 write( EOL + listNestingIndent + String.valueOf( STAR ) + String.valueOf( SPACE ) ); 446 } 447 else 448 { 449 write( EOL + listNestingIndent + String.valueOf( LEFT_SQUARE_BRACKET ) 450 + String.valueOf( LEFT_SQUARE_BRACKET ) + style + String.valueOf( RIGHT_SQUARE_BRACKET ) 451 + String.valueOf( RIGHT_SQUARE_BRACKET ) + String.valueOf( SPACE ) ); 452 } 453 itemFlag = true; 454 } 455 456 /** {@inheritDoc} */ 457 public void numberedListItem_() 458 { 459 write( EOL ); 460 itemFlag = false; 461 } 462 463 /** {@inheritDoc} */ 464 public void definitionList() 465 { 466 listNestingIndent += " "; 467 listStyles.push( "" ); 468 write( EOL ); 469 } 470 471 /** {@inheritDoc} */ 472 public void definitionList_() 473 { 474 if ( listNestingIndent.length() <= 1 ) 475 { 476 write( EOL + listNestingIndent + LIST_END_MARKUP + EOL ); 477 } 478 else 479 { 480 write( EOL ); 481 } 482 listNestingIndent = StringUtils.chomp( listNestingIndent, " " ); 483 listStyles.pop(); 484 itemFlag = false; 485 } 486 487 /** {@inheritDoc} */ 488 public void definedTerm() 489 { 490 write( EOL + " [" ); 491 } 492 493 /** {@inheritDoc} */ 494 public void definedTerm_() 495 { 496 write( "] " ); 497 } 498 499 /** {@inheritDoc} */ 500 public void definition() 501 { 502 itemFlag = true; 503 } 504 505 /** {@inheritDoc} */ 506 public void definition_() 507 { 508 write( EOL ); 509 itemFlag = false; 510 } 511 512 /** {@inheritDoc} */ 513 public void pageBreak() 514 { 515 write( EOL + PAGE_BREAK + EOL ); 516 } 517 518 /** {@inheritDoc} */ 519 public void paragraph() 520 { 521 if ( itemFlag ) 522 { 523 write( EOL + EOL + " " + listNestingIndent ); 524 } 525 else 526 { 527 write( EOL + " " ); 528 } 529 } 530 531 /** {@inheritDoc} */ 532 public void paragraph_() 533 { 534 write( EOL + EOL ); 535 } 536 537 /** {@inheritDoc} */ 538 public void verbatim( boolean boxed ) 539 { 540 verbatimFlag = true; 541 this.isBoxed = boxed; 542 write( EOL ); 543 if ( boxed ) 544 { 545 write( EOL + BOXED_VERBATIM_START_MARKUP + EOL ); 546 } 547 else 548 { 549 write( EOL + NON_BOXED_VERBATIM_START_MARKUP + EOL ); 550 } 551 } 552 553 /** {@inheritDoc} */ 554 public void verbatim_() 555 { 556 if ( isBoxed ) 557 { 558 write( EOL + BOXED_VERBATIM_END_MARKUP + EOL ); 559 } 560 else 561 { 562 write( EOL + NON_BOXED_VERBATIM_END_MARKUP + EOL ); 563 } 564 isBoxed = false; 565 verbatimFlag = false; 566 } 567 568 /** {@inheritDoc} */ 569 public void horizontalRule() 570 { 571 write( EOL + HORIZONTAL_RULE_MARKUP + EOL ); 572 } 573 574 /** {@inheritDoc} */ 575 public void table() 576 { 577 write( EOL ); 578 } 579 580 /** {@inheritDoc} */ 581 public void table_() 582 { 583 if ( rowLine != null ) 584 { 585 write( rowLine ); 586 } 587 rowLine = null; 588 589 if ( tableCaptionBuffer.length() > 0 ) 590 { 591 text( tableCaptionBuffer.toString() + EOL ); 592 } 593 594 resetTableCaptionBuffer(); 595 } 596 597 /** {@inheritDoc} */ 598 public void tableRows( int justification[], boolean grid ) 599 { 600 cellJustif = justification; 601 gridFlag = grid; 602 } 603 604 /** {@inheritDoc} */ 605 public void tableRows_() 606 { 607 cellJustif = null; 608 gridFlag = false; 609 } 610 611 /** {@inheritDoc} */ 612 public void tableRow() 613 { 614 bufferFlag = true; 615 cellCount = 0; 616 } 617 618 /** {@inheritDoc} */ 619 public void tableRow_() 620 { 621 bufferFlag = false; 622 623 // write out the header row first, then the data in the buffer 624 buildRowLine(); 625 626 write( rowLine ); 627 628 // TODO: This will need to be more clever, for multi-line cells 629 if ( gridFlag ) 630 { 631 write( TABLE_ROW_SEPARATOR_MARKUP ); 632 } 633 634 write( buffer.toString() ); 635 636 resetBuffer(); 637 638 write( EOL ); 639 640 // only reset cell count if this is the last row 641 cellCount = 0; 642 } 643 644 /** Construct a table row. */ 645 private void buildRowLine() 646 { 647 StringBuilder rLine = new StringBuilder(); 648 rLine.append( TABLE_ROW_START_MARKUP ); 649 650 for ( int i = 0; i < cellCount; i++ ) 651 { 652 if ( cellJustif != null ) 653 { 654 switch ( cellJustif[i] ) 655 { 656 case 1: 657 rLine.append( TABLE_COL_LEFT_ALIGNED_MARKUP ); 658 break; 659 case 2: 660 rLine.append( TABLE_COL_RIGHT_ALIGNED_MARKUP ); 661 break; 662 default: 663 rLine.append( TABLE_COL_CENTERED_ALIGNED_MARKUP ); 664 } 665 } 666 else 667 { 668 rLine.append( TABLE_COL_CENTERED_ALIGNED_MARKUP ); 669 } 670 } 671 rLine.append( EOL ); 672 673 this.rowLine = rLine.toString(); 674 } 675 676 /** {@inheritDoc} */ 677 public void tableCell() 678 { 679 tableCell( false ); 680 } 681 682 /** {@inheritDoc} */ 683 public void tableHeaderCell() 684 { 685 tableCell( true ); 686 } 687 688 /** 689 * Starts a table cell. 690 * 691 * @param headerRow If this cell is part of a header row. 692 */ 693 public void tableCell( boolean headerRow ) 694 { 695 if ( headerRow ) 696 { 697 buffer.append( TABLE_CELL_SEPARATOR_MARKUP ); 698 } 699 } 700 701 /** {@inheritDoc} */ 702 public void tableCell_() 703 { 704 endTableCell(); 705 } 706 707 /** {@inheritDoc} */ 708 public void tableHeaderCell_() 709 { 710 endTableCell(); 711 } 712 713 /** 714 * Ends a table cell. 715 */ 716 private void endTableCell() 717 { 718 buffer.append( TABLE_CELL_SEPARATOR_MARKUP ); 719 cellCount++; 720 } 721 722 /** {@inheritDoc} */ 723 public void tableCaption() 724 { 725 tableCaptionFlag = true; 726 } 727 728 /** {@inheritDoc} */ 729 public void tableCaption_() 730 { 731 tableCaptionFlag = false; 732 } 733 734 /** {@inheritDoc} */ 735 public void figureCaption_() 736 { 737 write( EOL ); 738 } 739 740 /** {@inheritDoc} */ 741 public void figureGraphics( String name ) 742 { 743 write( EOL + "[" + name + "] " ); 744 } 745 746 /** {@inheritDoc} */ 747 public void anchor( String name ) 748 { 749 write( ANCHOR_START_MARKUP ); 750 } 751 752 /** {@inheritDoc} */ 753 public void anchor_() 754 { 755 write( ANCHOR_END_MARKUP ); 756 } 757 758 /** {@inheritDoc} */ 759 public void link( String name ) 760 { 761 if ( !headerFlag ) 762 { 763 write( LINK_START_1_MARKUP ); 764 text( name.startsWith( "#" ) ? name.substring( 1 ) : name ); 765 write( LINK_START_2_MARKUP ); 766 } 767 } 768 769 /** {@inheritDoc} */ 770 public void link_() 771 { 772 if ( !headerFlag ) 773 { 774 write( LINK_END_MARKUP ); 775 } 776 } 777 778 /** 779 * A link with a target. 780 * 781 * @param name The name of the link. 782 * @param target The link target. 783 */ 784 public void link( String name, String target ) 785 { 786 if ( !headerFlag ) 787 { 788 write( LINK_START_1_MARKUP ); 789 text( target ); 790 write( LINK_START_2_MARKUP ); 791 text( name ); 792 } 793 } 794 795 /** {@inheritDoc} */ 796 public void italic() 797 { 798 if ( !headerFlag ) 799 { 800 write( ITALIC_START_MARKUP ); 801 } 802 } 803 804 /** {@inheritDoc} */ 805 public void italic_() 806 { 807 if ( !headerFlag ) 808 { 809 write( ITALIC_END_MARKUP ); 810 } 811 } 812 813 /** {@inheritDoc} */ 814 public void bold() 815 { 816 if ( !headerFlag ) 817 { 818 write( BOLD_START_MARKUP ); 819 } 820 } 821 822 /** {@inheritDoc} */ 823 public void bold_() 824 { 825 if ( !headerFlag ) 826 { 827 write( BOLD_END_MARKUP ); 828 } 829 } 830 831 /** {@inheritDoc} */ 832 public void monospaced() 833 { 834 if ( !headerFlag ) 835 { 836 write( MONOSPACED_START_MARKUP ); 837 } 838 } 839 840 /** {@inheritDoc} */ 841 public void monospaced_() 842 { 843 if ( !headerFlag ) 844 { 845 write( MONOSPACED_END_MARKUP ); 846 } 847 } 848 849 /** {@inheritDoc} */ 850 public void lineBreak() 851 { 852 if ( headerFlag || bufferFlag ) 853 { 854 buffer.append( EOL ); 855 } 856 else if ( verbatimFlag ) 857 { 858 write( EOL ); 859 } 860 else 861 { 862 write( "\\" + EOL ); 863 } 864 } 865 866 /** {@inheritDoc} */ 867 public void nonBreakingSpace() 868 { 869 if ( headerFlag || bufferFlag ) 870 { 871 buffer.append( NON_BREAKING_SPACE_MARKUP ); 872 } 873 else 874 { 875 write( NON_BREAKING_SPACE_MARKUP ); 876 } 877 } 878 879 /** {@inheritDoc} */ 880 public void text( String text ) 881 { 882 if ( tableCaptionFlag ) 883 { 884 tableCaptionBuffer.append( text ); 885 } 886 else if ( headerFlag || bufferFlag ) 887 { 888 buffer.append( text ); 889 } 890 else if ( verbatimFlag ) 891 { 892 verbatimContent( text ); 893 } 894 else 895 { 896 content( text ); 897 } 898 } 899 900 /** {@inheritDoc} */ 901 public void rawText( String text ) 902 { 903 write( text ); 904 } 905 906 /** {@inheritDoc} */ 907 public void comment( String comment ) 908 { 909 rawText( ( startFlag ? "" : EOL ) + COMMENT + COMMENT + comment ); 910 } 911 912 /** 913 * {@inheritDoc} 914 * 915 * Unkown events just log a warning message but are ignored otherwise. 916 * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes) 917 */ 918 public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes ) 919 { 920 getLog().warn( "[Apt Sink] Unknown Sink event: '" + name + "', ignoring!" ); 921 } 922 923 /** 924 * Write text to output. 925 * 926 * @param text The text to write. 927 */ 928 protected void write( String text ) 929 { 930 startFlag = false; 931 writer.write( unifyEOLs( text ) ); 932 } 933 934 /** 935 * Write Apt escaped text to output. 936 * 937 * @param text The text to write. 938 */ 939 protected void content( String text ) 940 { 941 write( escapeAPT( text ) ); 942 } 943 944 /** 945 * Write Apt escaped text to output. 946 * 947 * @param text The text to write. 948 */ 949 protected void verbatimContent( String text ) 950 { 951 write( escapeAPT( text ) ); 952 } 953 954 /** {@inheritDoc} */ 955 public void flush() 956 { 957 writer.flush(); 958 } 959 960 /** {@inheritDoc} */ 961 public void close() 962 { 963 writer.close(); 964 965 init(); 966 } 967 968 // ---------------------------------------------------------------------- 969 // Private methods 970 // ---------------------------------------------------------------------- 971 972 /** 973 * Escape special characters in a text in APT: 974 * 975 * <pre> 976 * \~, \=, \-, \+, \*, \[, \], \<, \>, \{, \}, \\ 977 * </pre> 978 * 979 * @param text the String to escape, may be null 980 * @return the text escaped, "" if null String input 981 */ 982 private static String escapeAPT( String text ) 983 { 984 if ( text == null ) 985 { 986 return ""; 987 } 988 989 int length = text.length(); 990 StringBuilder buffer = new StringBuilder( length ); 991 992 for ( int i = 0; i < length; ++i ) 993 { 994 char c = text.charAt( i ); 995 switch ( c ) 996 { // 0080 997 case '\\': 998 case '~': 999 case '=': 1000 case '-': 1001 case '+': 1002 case '*': 1003 case '[': 1004 case ']': 1005 case '<': 1006 case '>': 1007 case '{': 1008 case '}': 1009 buffer.append( '\\' ); 1010 buffer.append( c ); 1011 break; 1012 default: 1013 buffer.append( c ); 1014 } 1015 } 1016 1017 return buffer.toString(); 1018 } 1019}