2222package com .igormaznitsa .jcp .containers ;
2323
2424import static java .util .Objects .requireNonNull ;
25+ import static java .util .Objects .requireNonNullElse ;
2526
2627import com .igormaznitsa .jcp .context .CommentTextProcessor ;
2728import com .igormaznitsa .jcp .context .PreprocessingState ;
@@ -404,8 +405,37 @@ private String extractSingleDollarPrefixedDirective(final String line,
404405 return tail ;
405406 }
406407
408+ private static int findLastReadLineIndex (final PreprocessingState state ) {
409+ if (state == null ) {
410+ return -1 ;
411+ }
412+
413+ var fileData = state .peekFile ();
414+ if (fileData == null ) {
415+ return -1 ;
416+ } else {
417+ return fileData .getLastReadStringIndex ();
418+ }
419+ }
420+
421+ /**
422+ * Preprocess the file described by the object, <b>NB! it doesn't clear local variables automatically for cloned contexts</b>
423+ *
424+ * @param state the start preprocessing state, can be null
425+ * @param context the preprocessor context, must not be null
426+ * @return the state for the preprocessed file
427+ * @throws IOException it will be thrown for IO errors
428+ * @throws PreprocessorException it will be thrown for violation of preprocessing logic, like undefined variable
429+ * @see #preprocessFileWithNotification(PreprocessingState, PreprocessorContext, boolean)
430+ */
431+ public PreprocessingState preprocessFile (final PreprocessingState state ,
432+ final PreprocessorContext context ) throws IOException {
433+ return this .preprocessFileWithNotification (state , context , true );
434+ }
435+
407436 private void flushTextBufferForRemovedComments (
408437 final AtomicReference <Map .Entry <String , String >> firstDetectedUncommentLinePtr ,
438+ final int stringIndex ,
409439 final StringBuilder textBuffer ,
410440 final ResetablePrinter resetablePrinter ,
411441 final PreprocessingState state ,
@@ -424,9 +454,11 @@ private void flushTextBufferForRemovedComments(
424454 if (!processors .isEmpty ()) {
425455 processors .forEach (x -> {
426456 try {
457+ final FilePositionInfo filePositionInfo =
458+ new FilePositionInfo (this .sourceFile , stringIndex );
427459 final String result = x .onUncommentText (
428460 firstUncommentLine == null ? 0 : firstUncommentLine .getKey ().length (), origText ,
429- this , context , state );
461+ filePositionInfo , this , context , state );
430462 textBuffer .append (result );
431463 } catch (Exception ex ) {
432464 throw new PreprocessorException (
@@ -442,21 +474,6 @@ private void flushTextBufferForRemovedComments(
442474 }
443475 }
444476
445- /**
446- * Preprocess the file described by the object, <b>NB! it doesn't clear local variables automatically for cloned contexts</b>
447- *
448- * @param state the start preprocessing state, can be null
449- * @param context the preprocessor context, must not be null
450- * @return the state for the preprocessed file
451- * @throws IOException it will be thrown for IO errors
452- * @throws PreprocessorException it will be thrown for violation of preprocessing logic, like undefined variable
453- * @see #preprocessFileWithNotification(PreprocessingState, PreprocessorContext, boolean)
454- */
455- public PreprocessingState preprocessFile (final PreprocessingState state ,
456- final PreprocessorContext context ) throws IOException {
457- return this .preprocessFileWithNotification (state , context , true );
458- }
459-
460477 /**
461478 * Preprocess the file described by the object, <b>NB! it doesn't clear local variables automatically for cloned contexts</b>
462479 *
@@ -477,7 +494,7 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
477494 context .fireNotificationStart ();
478495 }
479496
480- PreprocessingState preprocessingState = null ;
497+ PreprocessingState theState = null ;
481498 Throwable error = null ;
482499 try {
483500 // do not clear local variables for cloned context to keep them in the new context
@@ -486,28 +503,29 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
486503 }
487504
488505 if (state == null ) {
489- preprocessingState = context .produceNewPreprocessingState (this , 1 );
506+ theState = context .produceNewPreprocessingState (this , 1 );
490507 } else {
491- preprocessingState = state ;
508+ theState = state ;
492509 }
493510
494511 String leftTrimmedString = null ;
495512
496513 TextFileDataContainer lastTextFileDataContainer = null ;
497514 final StringBuilder textBlockBuffer = new StringBuilder ();
498515
516+ Integer firstBlockLineIndex = null ;
499517 try {
500518 final AtomicReference <Map .Entry <String , String >> firstUncommentLine =
501519 new AtomicReference <>();
502520
503521 while (!Thread .currentThread ().isInterrupted ()) {
504522 final ResetablePrinter thePrinter =
505- requireNonNull (preprocessingState .getPrinter (), "Printer must be defined" );
523+ requireNonNull (theState .getPrinter (), "Printer must be defined" );
506524
507- String rawString = preprocessingState .nextLine ();
508- final boolean presentedNextLine = preprocessingState .hasReadLineNextLineInEnd ();
525+ String rawString = theState .nextLine ();
526+ final boolean presentedNextLine = theState .hasReadLineNextLineInEnd ();
509527
510- final Set <PreprocessingFlag > processFlags = preprocessingState .getPreprocessingFlags ();
528+ final Set <PreprocessingFlag > processFlags = theState .getPreprocessingFlags ();
511529
512530 if (processFlags .contains (PreprocessingFlag .END_PROCESSING ) ||
513531 processFlags .contains (PreprocessingFlag .ABORT_PROCESSING )) {
@@ -517,18 +535,22 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
517535 rawString = null ;
518536 }
519537
520- if (preprocessingState .getPreprocessingFlags ()
538+ if (theState .getPreprocessingFlags ()
521539 .contains (PreprocessingFlag .END_PROCESSING )) {
522- preprocessingState .getPreprocessingFlags ().remove (PreprocessingFlag .END_PROCESSING );
540+ theState .getPreprocessingFlags ().remove (PreprocessingFlag .END_PROCESSING );
523541 rawString = null ;
524542 }
525543
526544 if (rawString == null ) {
527- this .flushTextBufferForRemovedComments (firstUncommentLine , textBlockBuffer , thePrinter ,
528- preprocessingState ,
545+ this .flushTextBufferForRemovedComments (
546+ firstUncommentLine ,
547+ requireNonNullElse (firstBlockLineIndex , findLastReadLineIndex (theState )),
548+ textBlockBuffer , thePrinter ,
549+ theState ,
529550 context );
530- lastTextFileDataContainer = preprocessingState .popTextContainer ();
531- if (preprocessingState .isIncludeStackEmpty ()) {
551+ firstBlockLineIndex = null ;
552+ lastTextFileDataContainer = theState .popTextContainer ();
553+ if (theState .isIncludeStackEmpty ()) {
532554 break ;
533555 } else {
534556 continue ;
@@ -554,12 +576,16 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
554576 final boolean doPrintLn = presentedNextLine || !context .isCareForLastEol ();
555577
556578 if (isHashPrefixed (stringToBeProcessed , context )) {
557- this .flushTextBufferForRemovedComments (firstUncommentLine , textBlockBuffer , thePrinter ,
558- preprocessingState ,
579+ this .flushTextBufferForRemovedComments (firstUncommentLine ,
580+ requireNonNullElse (firstBlockLineIndex , findLastReadLineIndex (theState )),
581+ textBlockBuffer ,
582+ thePrinter ,
583+ theState ,
559584 context );
585+ firstBlockLineIndex = null ;
560586 final String extractedDirective =
561587 extractHashPrefixedDirective (stringToBeProcessed , context );
562- switch (processDirective (preprocessingState , extractedDirective , context , false )) {
588+ switch (processDirective (theState , extractedDirective , context , false )) {
563589 case PROCESSED :
564590 case READ_NEXT_LINE : {
565591 if (context .isKeepLines ()) {
@@ -590,8 +616,8 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
590616 }
591617 }
592618
593- if (preprocessingState .isDirectiveCanBeProcessed () &&
594- !preprocessingState .getPreprocessingFlags ()
619+ if (theState .isDirectiveCanBeProcessed () &&
620+ !theState .getPreprocessingFlags ()
595621 .contains (PreprocessingFlag .TEXT_OUTPUT_DISABLED )) {
596622 final boolean startsWithTwoDollars =
597623 isDoubleDollarPrefixed (leftTrimmedString , context .isAllowWhitespaces ());
@@ -614,24 +640,32 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
614640 indentText = Map .entry (context .isPreserveIndents () ? stringPrefix : "" , text );
615641
616642 if (firstLineSet ) {
643+ firstBlockLineIndex = findLastReadLineIndex (theState );
617644 firstUncommentLine .set (indentText );
618645 }
619646 textBlockBuffer .append (indentText .getValue ());
620647 if (doPrintLn ) {
621648 textBlockBuffer .append (context .getEol ());
622649 }
623650 } else {
624- this .flushTextBufferForRemovedComments (firstUncommentLine , textBlockBuffer ,
651+ this .flushTextBufferForRemovedComments (firstUncommentLine ,
652+ requireNonNullElse (firstBlockLineIndex , findLastReadLineIndex (theState )),
653+ textBlockBuffer ,
625654 thePrinter ,
626- preprocessingState , context );
655+ theState , context );
656+ firstBlockLineIndex = null ;
657+
627658 textBlockBuffer .append (stringPrefix ).append (indentText .getKey ())
628659 .append (indentText .getValue ());
629660 if (doPrintLn ) {
630661 textBlockBuffer .append (context .getEol ());
631662 }
632- this .flushTextBufferForRemovedComments (firstUncommentLine , textBlockBuffer ,
663+ this .flushTextBufferForRemovedComments (firstUncommentLine ,
664+ requireNonNullElse (firstBlockLineIndex , findLastReadLineIndex (theState )),
665+ textBlockBuffer ,
633666 thePrinter ,
634- preprocessingState , context );
667+ theState , context );
668+ firstBlockLineIndex = null ;
635669 }
636670 } else if (isSingleDollarPrefixed (stringToBeProcessed , context .isAllowWhitespaces ())) {
637671 // Output the tail of the string to the output stream without comments
@@ -648,6 +682,7 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
648682 indentText = Map .entry (context .isPreserveIndents () ? stringPrefix : "" , text );
649683
650684 if (firstLineSet ) {
685+ firstBlockLineIndex = findLastReadLineIndex (theState );
651686 firstUncommentLine .set (indentText );
652687 }
653688
@@ -656,30 +691,41 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
656691 textBlockBuffer .append (context .getEol ());
657692 }
658693 } else {
659- this .flushTextBufferForRemovedComments (firstUncommentLine , textBlockBuffer ,
694+ this .flushTextBufferForRemovedComments (firstUncommentLine ,
695+ requireNonNullElse (firstBlockLineIndex , findLastReadLineIndex (theState )),
696+ textBlockBuffer ,
660697 thePrinter ,
661- preprocessingState , context );
698+ theState , context );
699+ firstBlockLineIndex = null ;
700+
662701 textBlockBuffer .append (stringPrefix ).append (indentText .getKey ())
663702 .append (indentText .getValue ());
664703 if (doPrintLn ) {
665704 textBlockBuffer .append (context .getEol ());
666705 }
667- this .flushTextBufferForRemovedComments (firstUncommentLine , textBlockBuffer ,
706+ this .flushTextBufferForRemovedComments (
707+ firstUncommentLine ,
708+ requireNonNullElse (firstBlockLineIndex , findLastReadLineIndex (theState )),
709+ textBlockBuffer ,
668710 thePrinter ,
669- preprocessingState , context );
711+ theState , context );
712+ firstBlockLineIndex = null ;
670713 }
671714 } else {
672715 // Just string
673- this .flushTextBufferForRemovedComments (firstUncommentLine , textBlockBuffer ,
716+ this .flushTextBufferForRemovedComments (firstUncommentLine ,
717+ requireNonNullElse (firstBlockLineIndex , findLastReadLineIndex (theState )),
718+ textBlockBuffer ,
674719 thePrinter ,
675- preprocessingState , context );
720+ theState , context );
721+ firstBlockLineIndex = null ;
676722
677723 final String strToOut = findTailRemover (stringToBeProcessed , context );
678724
679- if (preprocessingState .getPreprocessingFlags ()
725+ if (theState .getPreprocessingFlags ()
680726 .contains (PreprocessingFlag .COMMENT_NEXT_LINE )) {
681727 thePrinter .print (AbstractDirectiveHandler .ONE_LINE_COMMENT );
682- preprocessingState .getPreprocessingFlags ()
728+ theState .getPreprocessingFlags ()
683729 .remove (PreprocessingFlag .COMMENT_NEXT_LINE );
684730 }
685731
@@ -691,9 +737,15 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
691737 }
692738 }
693739 } else if (context .isKeepLines ()) {
694- flushTextBufferForRemovedComments (firstUncommentLine , textBlockBuffer , thePrinter ,
695- preprocessingState ,
740+
741+ flushTextBufferForRemovedComments (firstUncommentLine ,
742+ requireNonNullElse (firstBlockLineIndex , findLastReadLineIndex (theState )),
743+ textBlockBuffer ,
744+ thePrinter ,
745+ theState ,
696746 context );
747+ firstBlockLineIndex = null ;
748+
697749 final String text = AbstractDirectiveHandler .PREFIX_FOR_KEEPING_LINES + rawString ;
698750 if (doPrintLn ) {
699751 thePrinter .println (text , context .getEol ());
@@ -705,20 +757,20 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
705757 } catch (Exception unexpected ) {
706758 final String message =
707759 unexpected .getMessage () == null ? "Unexpected exception" : unexpected .getMessage ();
708- throw preprocessingState .makeException (message , leftTrimmedString , unexpected );
760+ throw theState .makeException (message , leftTrimmedString , unexpected );
709761 }
710762
711- if (!preprocessingState .isIfStackEmpty ()) {
763+ if (!theState .isIfStackEmpty ()) {
712764 final TextFileDataContainer lastIf =
713- requireNonNull (preprocessingState .peekIf (), "'IF' stack is empty" );
765+ requireNonNull (theState .peekIf (), "'IF' stack is empty" );
714766 throw new PreprocessorException (
715767 "Unclosed " + AbstractDirectiveHandler .DIRECTIVE_PREFIX + "if instruction detected" ,
716768 "" , new FilePositionInfo [] {
717769 new FilePositionInfo (lastIf .getFile (), lastIf .getNextStringIndex ())}, null );
718770 }
719- if (!preprocessingState .isWhileStackEmpty ()) {
771+ if (!theState .isWhileStackEmpty ()) {
720772 final TextFileDataContainer lastWhile =
721- requireNonNull (preprocessingState .peekWhile (), "'WHILE' stack is empty" );
773+ requireNonNull (theState .peekWhile (), "'WHILE' stack is empty" );
722774 throw new PreprocessorException (
723775 "Unclosed " + AbstractDirectiveHandler .DIRECTIVE_PREFIX + "while instruction detected" ,
724776 "" , new FilePositionInfo [] {
@@ -729,7 +781,7 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
729781 final File outFile = context .createDestinationFileForPath (makeTargetFilePathAsString ());
730782
731783 final boolean wasSaved =
732- preprocessingState .saveBuffersToFile (outFile , context .getKeepComments ());
784+ theState .saveBuffersToFile (outFile , context .getKeepComments ());
733785
734786 if (context .isVerbose ()) {
735787 context .logForVerbose (String
@@ -760,7 +812,7 @@ public PreprocessingState preprocessFileWithNotification(final PreprocessingStat
760812 context .fireNotificationStop (error );
761813 }
762814 }
763- return preprocessingState ;
815+ return theState ;
764816 }
765817
766818 private boolean checkDirectiveArgumentRoughly (final AbstractDirectiveHandler directive ,
0 commit comments