如何识别并统计Java特定条件注释内的缩进模式?
Problem Breakdown
We need to analyze a single Java file containing conditional comment blocks wrapped in //#if defined(IDENTIFIER) and //#endif. Each block falls into one of two indent patterns:
- Bubble: Indentation follows a nested "peak" shape (e.g., 4→8→12→8→4)
- Straight: All lines in the block have 0 indentation
Our goals are to:
- Identify the indent pattern for each block
- Aggregate counts per identifier (e.g.,
LOGGING: Bubble 1, Straight 1) - Optimize or implement this logic using an existing
lineIndentsmethod that retrieves a line's indentation.
Implementation & Optimization Suggestions
Step 1: Locate Conditional Blocks
First, scan the file line by line to track block boundaries:
- Trigger block start when encountering
//#if defined(...); extract the identifier (likeLOGGING) from this line - Use a stack to handle nested blocks (critical if your code has nested conditional comments)
- Capture all lines between the start
//#ifand its matching//#endif
Step 2: Extract Relevant Indent Data
For each line inside the block (excluding the start/end comment lines):
- Use
lineIndentsto get the indent value - Filter out empty lines or comment-only lines—these can skew pattern detection
Step 3: Classify the Indent Pattern
Straight Pattern Check
A block is Straight if every non-empty, non-comment line has an indentation of 0. Optimize this by terminating early: as soon as you find a line with non-zero indent, mark it as not Straight.
Bubble Pattern Check
A Bubble requires indentation to strictly increase to a peak, then strictly decrease back. Here's how to detect it:
- Find the peak indent value and its position in the block
- Verify all lines before the peak have strictly increasing indent
- Verify all lines after the peak have strictly decreasing indent
- Edge case: Blocks with only 1 or 2 lines can't form a bubble—mark these as
UNKNOWNor handle them per your requirements
Step 4: Aggregate Statistics
Use a nested map (or custom data class) to track counts:
- Outer map key: The identifier (e.g.,
LOGGING) - Inner map keys:
BubbleandStraight, with values as their respective counts
Optimization Tips
- Cache Indent Values: If
lineIndentsis expensive (e.g., reads the file each time), precompute and cache indent values for all lines upfront. This avoids redundant calls. - Early Pattern Termination: For both patterns, stop checking as soon as the pattern is invalidated (e.g., non-zero indent for Straight, a non-increasing line before the peak for Bubble).
- Nested Block Handling: Use a stack to track block depth—only process lines for the current top-level block unless you need to analyze nested blocks separately.
Example Pseudocode Snippet
// Precompute indent values for all lines (optimization) List<Integer> allLineIndents = new ArrayList<>(); for (String line : Files.readAllLines(Paths.get("your-file.java"))) { allLineIndents.add(lineIndents(line)); } // Stats storage Map<String, PatternStats> identifierStats = new HashMap<>(); Deque<String> blockStack = new ArrayDeque<>(); List<Integer> currentBlockIndents = new ArrayList<>(); String currentIdentifier = null; // Scan lines to process blocks List<String> allLines = Files.readAllLines(Paths.get("your-file.java")); for (int i = 0; i < allLines.size(); i++) { String line = allLines.get(i).trim(); if (line.startsWith("//#if defined(")) { // Extract identifier int start = line.indexOf("defined(") + 8; int end = line.indexOf(")", start); currentIdentifier = line.substring(start, end).trim(); blockStack.push(currentIdentifier); currentBlockIndents.clear(); } else if (line.startsWith("//#endif")) { if (!blockStack.isEmpty()) { blockStack.pop(); // Classify and update stats Pattern pattern = classifyPattern(currentBlockIndents); identifierStats.computeIfAbsent(currentIdentifier, k -> new PatternStats()) .increment(pattern); currentIdentifier = blockStack.isEmpty() ? null : blockStack.peek(); currentBlockIndents.clear(); } } else if (!blockStack.isEmpty()) { // Only collect indents for non-empty, non-comment lines String rawLine = allLines.get(i); if (!rawLine.trim().isEmpty() && !rawLine.trim().startsWith("//")) { currentBlockIndents.add(allLineIndents.get(i)); } } } // Helper: Classify pattern private Pattern classifyPattern(List<Integer> indents) { if (indents.size() < 3) return Pattern.UNKNOWN; // Check Straight boolean isStraight = indents.stream().allMatch(indent -> indent == 0); if (isStraight) return Pattern.STRAIGHT; // Check Bubble int peakValue = Collections.max(indents); int peakIndex = indents.indexOf(peakValue); // Verify increasing up to peak boolean increasing = true; for (int i = 1; i <= peakIndex; i++) { if (indents.get(i) <= indents.get(i-1)) { increasing = false; break; } } if (!increasing) return Pattern.UNKNOWN; // Verify decreasing after peak boolean decreasing = true; for (int i = peakIndex + 1; i < indents.size(); i++) { if (indents.get(i) >= indents.get(i-1)) { decreasing = false; break; } } return increasing && decreasing ? Pattern.BUBBLE : Pattern.UNKNOWN; } // Helper classes enum Pattern { BUBBLE, STRAIGHT, UNKNOWN } class PatternStats { int bubbleCount = 0; int straightCount = 0; void increment(Pattern pattern) { switch(pattern) { case BUBBLE: bubbleCount++; break; case STRAIGHT: straightCount++; break; default: break; } } @Override public String toString() { return String.format("Bubble %d, Straight %d", bubbleCount, straightCount); } } // Print results identifierStats.forEach((id, stats) -> { System.out.printf("%s: %s%n", id, stats); });
Key Notes
- Edge Cases: Define how to handle empty blocks, single-line blocks, or blocks that don't fit either pattern (add an
UNKNOWNcategory if needed). - Accuracy: Ensure you're only analyzing code lines—skip comments and empty lines to avoid false pattern matches.
- Performance: For large files, precomputing indent values is a must to avoid redundant I/O or processing.
内容的提问来源于stack exchange,提问作者Avah

