1717
1818package org .checkstyle .autofix .recipe ;
1919
20+ import java .nio .file .Path ;
21+ import java .util .ArrayList ;
22+ import java .util .List ;
23+ import java .util .concurrent .CancellationException ;
24+ import java .util .function .Function ;
25+
26+ import org .checkstyle .autofix .parser .CheckstyleViolation ;
27+ import org .openrewrite .Cursor ;
2028import org .openrewrite .ExecutionContext ;
29+ import org .openrewrite .PrintOutputCapture ;
2130import org .openrewrite .Recipe ;
2231import org .openrewrite .TreeVisitor ;
32+ import org .openrewrite .internal .RecipeRunException ;
2333import org .openrewrite .java .JavaIsoVisitor ;
2434import org .openrewrite .java .tree .J ;
2535import org .openrewrite .java .tree .JavaType ;
3040 */
3141public class UpperEll extends Recipe {
3242
43+ private final List <CheckstyleViolation > violations ;
44+
45+ public UpperEll () {
46+ this (new ArrayList <>());
47+ }
48+
49+ public UpperEll (List <CheckstyleViolation > violations ) {
50+ this .violations = violations ;
51+ }
52+
3353 @ Override
3454 public String getDisplayName () {
3555 return "UpperEll recipe" ;
@@ -46,20 +66,121 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
4666 return new UpperEllVisitor ();
4767 }
4868
49- /**
50- * Visitor that replaces lowercase 'l' suffixes in long literals with uppercase 'L'.
51- */
52- private static final class UpperEllVisitor extends JavaIsoVisitor <ExecutionContext > {
69+ private final class UpperEllVisitor extends JavaIsoVisitor <ExecutionContext > {
70+
71+ private static final String LOWERCASE_L = "l" ;
72+ private static final String UPPERCASE_L = "L" ;
73+
74+ private Path sourcePath ;
75+
76+ @ Override
77+ public J .CompilationUnit visitCompilationUnit (J .CompilationUnit cu , ExecutionContext ctx ) {
78+ this .sourcePath = cu .getSourcePath ();
79+ return super .visitCompilationUnit (cu , ctx );
80+ }
81+
5382 @ Override
5483 public J .Literal visitLiteral (J .Literal literal , ExecutionContext ctx ) {
5584 J .Literal result = super .visitLiteral (literal , ctx );
5685 final String valueSource = result .getValueSource ();
5786
58- if (valueSource != null && valueSource .endsWith ("l" )
59- && result .getType () == JavaType .Primitive .Long ) {
87+ if (valueSource != null && valueSource .endsWith (LOWERCASE_L )
88+ && result .getType () == JavaType .Primitive .Long
89+ && isAtViolationLocation (result )) {
90+
6091 final String numericPart = valueSource .substring (0 , valueSource .length () - 1 );
61- final String newValueSource = numericPart + "L" ;
62- result = result .withValueSource (newValueSource );
92+ result = result .withValueSource (numericPart + UPPERCASE_L );
93+ }
94+
95+ return result ;
96+ }
97+
98+ private boolean isAtViolationLocation (J .Literal literal ) {
99+ final J .CompilationUnit cursor = getCursor ().firstEnclosing (J .CompilationUnit .class );
100+
101+ final int line = computeLinePosition (cursor , literal , getCursor ());
102+ final int column = computeColumnPosition (cursor , literal , getCursor ());
103+
104+ return violations .stream ().anyMatch (violation -> {
105+ return violation .getLine () == line
106+ && violation .getColumn () == column
107+ && Path .of (violation .getFileName ()).equals (sourcePath );
108+ });
109+ }
110+
111+ /**
112+ * Computes the position of a target element within a syntax tree using position calculator.
113+ * This method traverses the given syntax tree and captures the printed output until the
114+ * target element is encountered. When the target is found, a CancellationException
115+ * is thrown to interrupt traversal, and the captured output is passed to the provided
116+ * positionCalculator to compute the position.
117+ *
118+ * @param tree the root of the syntax tree to traverse
119+ * @param targetElement the element whose position is to be computed
120+ * @param cursor the current cursor in the tree traversal
121+ * @param positionCalculator a function to compute the position from the printed output
122+ * @return the computed position of the target element
123+ * @throws IllegalStateException if the target element is not found in the tree
124+ * @throws RecipeRunException if an error occurs during traversal
125+ */
126+ private int computePosition (
127+ J tree ,
128+ J targetElement ,
129+ Cursor cursor ,
130+ Function <String , Integer > positionCalculator
131+ ) {
132+ final TreeVisitor <?, PrintOutputCapture <TreeVisitor <?, ?>>> printer =
133+ tree .printer (cursor );
134+
135+ final PrintOutputCapture <TreeVisitor <?, ?>> capture =
136+ new PrintOutputCapture <>(printer ) {
137+ @ Override
138+ public PrintOutputCapture <TreeVisitor <?, ?>> append (String text ) {
139+ if (targetElement .isScope (getContext ().getCursor ().getValue ())) {
140+ super .append (targetElement .getPrefix ().getWhitespace ());
141+ throw new CancellationException ();
142+ }
143+ return super .append (text );
144+ }
145+ };
146+
147+ final int result ;
148+ try {
149+ printer .visit (tree , capture , cursor .getParentOrThrow ());
150+ throw new IllegalStateException ("Target element: " + targetElement
151+ + ", not found in the syntax tree." );
152+ }
153+ catch (CancellationException exception ) {
154+ result = positionCalculator .apply (capture .getOut ());
155+ }
156+ catch (RecipeRunException exception ) {
157+ if (exception .getCause () instanceof CancellationException ) {
158+ result = positionCalculator .apply (capture .getOut ());
159+ }
160+ else {
161+ throw exception ;
162+ }
163+ }
164+ return result ;
165+ }
166+
167+ private int computeLinePosition (J tree , J targetElement , Cursor cursor ) {
168+ return computePosition (tree , targetElement , cursor ,
169+ out -> 1 + Math .toIntExact (out .chars ().filter (chr -> chr == '\n' ).count ()));
170+ }
171+
172+ private int computeColumnPosition (J tree , J targetElement , Cursor cursor ) {
173+ return computePosition (tree , targetElement , cursor , this ::calculateColumnOffset );
174+ }
175+
176+ private int calculateColumnOffset (String out ) {
177+ final int lineBreakIndex = out .lastIndexOf ('\n' );
178+ final int result ;
179+ if (lineBreakIndex == -1 ) {
180+ result = out .length ();
181+ }
182+ else {
183+ result = out .length () - lineBreakIndex - 1 ;
63184 }
64185 return result ;
65186 }
0 commit comments