• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.google.googlejavaformat;
16 
17 import static com.google.common.collect.Iterables.getLast;
18 import static java.lang.Math.max;
19 
20 import com.google.common.base.MoreObjects;
21 import com.google.common.collect.DiscreteDomain;
22 import com.google.common.collect.Iterators;
23 import com.google.common.collect.Range;
24 import com.google.googlejavaformat.Output.BreakTag;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Optional;
28 
29 /**
30  * {@link com.google.googlejavaformat.java.JavaInputAstVisitor JavaInputAstVisitor} outputs a
31  * sequence of {@link Op}s using {@link OpsBuilder}. This linear sequence is then transformed by
32  * {@link DocBuilder} into a tree-structured {@code Doc}. The top-level {@code Doc} is a {@link
33  * Level}, which contains a sequence of {@code Doc}s, including other {@link Level}s. Leaf {@code
34  * Doc}s are {@link Token}s, representing language-level tokens; {@link Tok}s, which may also
35  * represent non-token {@link Input.Tok}s, including comments and other white-space; {@link Space}s,
36  * representing single spaces; and {@link Break}s, which represent optional line-breaks.
37  */
38 public abstract class Doc {
39   /**
40    * Each {@link Break} in a {@link Level} is either {@link FillMode#UNIFIED} or {@link
41    * FillMode#INDEPENDENT}.
42    */
43   public enum FillMode {
44     /**
45      * If a {@link Level} will not fit on one line, all of its {@code UNIFIED} {@link Break}s will
46      * be broken.
47      */
48     UNIFIED,
49 
50     /**
51      * If a {@link Level} will not fit on one line, its {@code INDEPENDENT} {@link Break}s will be
52      * broken independently of each other, to fill in the {@link Level}.
53      */
54     INDEPENDENT,
55 
56     /**
57      * A {@code FORCED} {@link Break} will always be broken, and a {@link Level} it appears in will
58      * not fit on one line.
59      */
60     FORCED
61   }
62 
63   /** State for writing. */
64   public static final class State {
65     final int lastIndent;
66     final int indent;
67     final int column;
68     final boolean mustBreak;
69 
State(int lastIndent, int indent, int column, boolean mustBreak)70     State(int lastIndent, int indent, int column, boolean mustBreak) {
71       this.lastIndent = lastIndent;
72       this.indent = indent;
73       this.column = column;
74       this.mustBreak = mustBreak;
75     }
76 
State(int indent0, int column0)77     public State(int indent0, int column0) {
78       this(indent0, indent0, column0, false);
79     }
80 
withColumn(int column)81     State withColumn(int column) {
82       return new State(lastIndent, indent, column, mustBreak);
83     }
84 
withMustBreak(boolean mustBreak)85     State withMustBreak(boolean mustBreak) {
86       return new State(lastIndent, indent, column, mustBreak);
87     }
88 
89     @Override
toString()90     public String toString() {
91       return MoreObjects.toStringHelper(this)
92           .add("lastIndent", lastIndent)
93           .add("indent", indent)
94           .add("column", column)
95           .add("mustBreak", mustBreak)
96           .toString();
97     }
98   }
99 
100   private static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1);
101   private static final DiscreteDomain<Integer> INTEGERS = DiscreteDomain.integers();
102 
103   // Memoized width; Float.POSITIVE_INFINITY if contains forced breaks.
104   private boolean widthComputed = false;
105   private float width = 0.0F;
106 
107   // Memoized flat; not defined (and never computed) if contains forced breaks.
108   private boolean flatComputed = false;
109   private String flat = "";
110 
111   // Memoized Range.
112   private boolean rangeComputed = false;
113   private Range<Integer> range = EMPTY_RANGE;
114 
115   /**
116    * Return the width of a {@code Doc}, or {@code Float.POSITIVE_INFINITY} if it must be broken.
117    *
118    * @return the width
119    */
getWidth()120   final float getWidth() {
121     if (!widthComputed) {
122       width = computeWidth();
123       widthComputed = true;
124     }
125     return width;
126   }
127 
128   /**
129    * Return a {@code Doc}'s flat-string value; not defined (and never called) if the (@code Doc}
130    * contains forced breaks.
131    *
132    * @return the flat-string value
133    */
getFlat()134   final String getFlat() {
135     if (!flatComputed) {
136       flat = computeFlat();
137       flatComputed = true;
138     }
139     return flat;
140   }
141 
142   /**
143    * Return the {@link Range} of a {@code Doc}.
144    *
145    * @return the {@code Doc}'s {@link Range}
146    */
range()147   final Range<Integer> range() {
148     if (!rangeComputed) {
149       range = computeRange();
150       rangeComputed = true;
151     }
152     return range;
153   }
154 
155   /**
156    * Compute the {@code Doc}'s width.
157    *
158    * @return the width, or {@code Float.POSITIVE_INFINITY} if it must be broken
159    */
computeWidth()160   abstract float computeWidth();
161 
162   /**
163    * Compute the {@code Doc}'s flat value. Not defined (and never called) if contains forced breaks.
164    *
165    * @return the flat value
166    */
computeFlat()167   abstract String computeFlat();
168 
169   /**
170    * Compute the {@code Doc}'s {@link Range} of {@link Input.Token}s.
171    *
172    * @return the {@link Range}
173    */
computeRange()174   abstract Range<Integer> computeRange();
175 
176   /**
177    * Make breaking decisions for a {@code Doc}.
178    *
179    * @param maxWidth the maximum line width
180    * @param state the current output state
181    * @return the new output state
182    */
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)183   public abstract State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state);
184 
185   /** Write a {@code Doc} to an {@link Output}, after breaking decisions have been made. */
write(Output output)186   public abstract void write(Output output);
187 
188   /** A {@code Level} inside a {@link Doc}. */
189   static final class Level extends Doc {
190     private final Indent plusIndent; // The extra indent following breaks.
191     private final List<Doc> docs = new ArrayList<>(); // The elements of the level.
192 
Level(Indent plusIndent)193     private Level(Indent plusIndent) {
194       this.plusIndent = plusIndent;
195     }
196 
197     /**
198      * Factory method for {@code Level}s.
199      *
200      * @param plusIndent the extra indent inside the {@code Level}
201      * @return the new {@code Level}
202      */
make(Indent plusIndent)203     static Level make(Indent plusIndent) {
204       return new Level(plusIndent);
205     }
206 
207     /**
208      * Add a {@link Doc} to the {@code Level}.
209      *
210      * @param doc the {@link Doc} to add
211      */
add(Doc doc)212     void add(Doc doc) {
213       docs.add(doc);
214     }
215 
216     @Override
computeWidth()217     float computeWidth() {
218       float thisWidth = 0.0F;
219       for (Doc doc : docs) {
220         thisWidth += doc.getWidth();
221       }
222       return thisWidth;
223     }
224 
225     @Override
computeFlat()226     String computeFlat() {
227       StringBuilder builder = new StringBuilder();
228       for (Doc doc : docs) {
229         builder.append(doc.getFlat());
230       }
231       return builder.toString();
232     }
233 
234     @Override
computeRange()235     Range<Integer> computeRange() {
236       Range<Integer> docRange = EMPTY_RANGE;
237       for (Doc doc : docs) {
238         docRange = union(docRange, doc.range());
239       }
240       return docRange;
241     }
242 
243     // State that needs to be preserved between calculating breaks and
244     // writing output.
245     // TODO(cushon): represent phases as separate immutable data.
246 
247     /** True if the entire {@link Level} fits on one line. */
248     boolean oneLine = false;
249 
250     /**
251      * Groups of {@link Doc}s that are children of the current {@link Level}, separated by {@link
252      * Break}s.
253      */
254     List<List<Doc>> splits = new ArrayList<>();
255 
256     /** {@link Break}s between {@link Doc}s in the current {@link Level}. */
257     List<Break> breaks = new ArrayList<>();
258 
259     @Override
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)260     public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
261       float thisWidth = getWidth();
262       if (state.column + thisWidth <= maxWidth) {
263         oneLine = true;
264         return state.withColumn(state.column + (int) thisWidth);
265       }
266       State broken =
267           computeBroken(
268               commentsHelper, maxWidth, new State(state.indent + plusIndent.eval(), state.column));
269       return state.withColumn(broken.column);
270     }
271 
splitByBreaks(List<Doc> docs, List<List<Doc>> splits, List<Break> breaks)272     private static void splitByBreaks(List<Doc> docs, List<List<Doc>> splits, List<Break> breaks) {
273       splits.clear();
274       breaks.clear();
275       splits.add(new ArrayList<>());
276       for (Doc doc : docs) {
277         if (doc instanceof Break) {
278           breaks.add((Break) doc);
279           splits.add(new ArrayList<>());
280         } else {
281           getLast(splits).add(doc);
282         }
283       }
284     }
285 
286     /** Compute breaks for a {@link Level} that spans multiple lines. */
computeBroken(CommentsHelper commentsHelper, int maxWidth, State state)287     private State computeBroken(CommentsHelper commentsHelper, int maxWidth, State state) {
288       splitByBreaks(docs, splits, breaks);
289 
290       state =
291           computeBreakAndSplit(
292               commentsHelper, maxWidth, state, /* optBreakDoc= */ Optional.empty(), splits.get(0));
293 
294       // Handle following breaks and split.
295       for (int i = 0; i < breaks.size(); i++) {
296         state =
297             computeBreakAndSplit(
298                 commentsHelper, maxWidth, state, Optional.of(breaks.get(i)), splits.get(i + 1));
299       }
300       return state;
301     }
302 
303     /** Lay out a Break-separated group of Docs in the current Level. */
computeBreakAndSplit( CommentsHelper commentsHelper, int maxWidth, State state, Optional<Break> optBreakDoc, List<Doc> split)304     private static State computeBreakAndSplit(
305         CommentsHelper commentsHelper,
306         int maxWidth,
307         State state,
308         Optional<Break> optBreakDoc,
309         List<Doc> split) {
310       float breakWidth = optBreakDoc.isPresent() ? optBreakDoc.get().getWidth() : 0.0F;
311       float splitWidth = getWidth(split);
312       boolean shouldBreak =
313           (optBreakDoc.isPresent() && optBreakDoc.get().fillMode == FillMode.UNIFIED)
314               || state.mustBreak
315               || state.column + breakWidth + splitWidth > maxWidth;
316 
317       if (optBreakDoc.isPresent()) {
318         state = optBreakDoc.get().computeBreaks(state, state.lastIndent, shouldBreak);
319       }
320       boolean enoughRoom = state.column + splitWidth <= maxWidth;
321       state = computeSplit(commentsHelper, maxWidth, split, state.withMustBreak(false));
322       if (!enoughRoom) {
323         state = state.withMustBreak(true); // Break after, too.
324       }
325       return state;
326     }
327 
computeSplit( CommentsHelper commentsHelper, int maxWidth, List<Doc> docs, State state)328     private static State computeSplit(
329         CommentsHelper commentsHelper, int maxWidth, List<Doc> docs, State state) {
330       for (Doc doc : docs) {
331         state = doc.computeBreaks(commentsHelper, maxWidth, state);
332       }
333       return state;
334     }
335 
336     @Override
write(Output output)337     public void write(Output output) {
338       if (oneLine) {
339         output.append(getFlat(), range()); // This is defined because width is finite.
340       } else {
341         writeFilled(output);
342       }
343     }
344 
writeFilled(Output output)345     private void writeFilled(Output output) {
346       // Handle first split.
347       for (Doc doc : splits.get(0)) {
348         doc.write(output);
349       }
350       // Handle following breaks and split.
351       for (int i = 0; i < breaks.size(); i++) {
352         breaks.get(i).write(output);
353         for (Doc doc : splits.get(i + 1)) {
354           doc.write(output);
355         }
356       }
357     }
358 
359     /**
360      * Get the width of a sequence of {@link Doc}s.
361      *
362      * @param docs the {@link Doc}s
363      * @return the width, or {@code Float.POSITIVE_INFINITY} if any {@link Doc} must be broken
364      */
getWidth(List<Doc> docs)365     static float getWidth(List<Doc> docs) {
366       float width = 0.0F;
367       for (Doc doc : docs) {
368         width += doc.getWidth();
369       }
370       return width;
371     }
372 
union(Range<Integer> x, Range<Integer> y)373     private static Range<Integer> union(Range<Integer> x, Range<Integer> y) {
374       return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(INTEGERS);
375     }
376 
377     @Override
toString()378     public String toString() {
379       return MoreObjects.toStringHelper(this)
380           .add("plusIndent", plusIndent)
381           .add("docs", docs)
382           .toString();
383     }
384   }
385 
386   /** A leaf {@link Doc} for a token. */
387   public static final class Token extends Doc implements Op {
388     /** Is a Token a real token, or imaginary (e.g., a token generated incorrectly, or an EOF)? */
389     public enum RealOrImaginary {
390       REAL,
391       IMAGINARY;
392 
isReal()393       boolean isReal() {
394         return this == REAL;
395       }
396     }
397 
398     private final Input.Token token;
399     private final RealOrImaginary realOrImaginary;
400     private final Indent plusIndentCommentsBefore;
401     private final Optional<Indent> breakAndIndentTrailingComment;
402 
Token( Input.Token token, RealOrImaginary realOrImaginary, Indent plusIndentCommentsBefore, Optional<Indent> breakAndIndentTrailingComment)403     private Token(
404         Input.Token token,
405         RealOrImaginary realOrImaginary,
406         Indent plusIndentCommentsBefore,
407         Optional<Indent> breakAndIndentTrailingComment) {
408       this.token = token;
409       this.realOrImaginary = realOrImaginary;
410       this.plusIndentCommentsBefore = plusIndentCommentsBefore;
411       this.breakAndIndentTrailingComment = breakAndIndentTrailingComment;
412     }
413 
414     /**
415      * How much extra to indent comments before the {@code Token}.
416      *
417      * @return the extra indent
418      */
getPlusIndentCommentsBefore()419     Indent getPlusIndentCommentsBefore() {
420       return plusIndentCommentsBefore;
421     }
422 
423     /** Force a line break and indent trailing javadoc or block comments. */
breakAndIndentTrailingComment()424     Optional<Indent> breakAndIndentTrailingComment() {
425       return breakAndIndentTrailingComment;
426     }
427 
428     /**
429      * Make a {@code Token}.
430      *
431      * @param token the {@link Input.Token} to wrap
432      * @param realOrImaginary did this {@link Input.Token} appear in the input, or was it generated
433      *     incorrectly?
434      * @param plusIndentCommentsBefore extra {@code plusIndent} for comments just before this token
435      * @return the new {@code Token}
436      */
make( Input.Token token, Doc.Token.RealOrImaginary realOrImaginary, Indent plusIndentCommentsBefore, Optional<Indent> breakAndIndentTrailingComment)437     static Op make(
438         Input.Token token,
439         Doc.Token.RealOrImaginary realOrImaginary,
440         Indent plusIndentCommentsBefore,
441         Optional<Indent> breakAndIndentTrailingComment) {
442       return new Token(
443           token, realOrImaginary, plusIndentCommentsBefore, breakAndIndentTrailingComment);
444     }
445 
446     /**
447      * Return the wrapped {@link Input.Token}.
448      *
449      * @return the {@link Input.Token}
450      */
getToken()451     Input.Token getToken() {
452       return token;
453     }
454 
455     /**
456      * Is the token good? That is, does it match an {@link Input.Token}?
457      *
458      * @return whether the @code Token} is good
459      */
realOrImaginary()460     RealOrImaginary realOrImaginary() {
461       return realOrImaginary;
462     }
463 
464     @Override
add(DocBuilder builder)465     public void add(DocBuilder builder) {
466       builder.add(this);
467     }
468 
469     @Override
computeWidth()470     float computeWidth() {
471       return token.getTok().length();
472     }
473 
474     @Override
computeFlat()475     String computeFlat() {
476       return token.getTok().getOriginalText();
477     }
478 
479     @Override
computeRange()480     Range<Integer> computeRange() {
481       return Range.singleton(token.getTok().getIndex()).canonical(INTEGERS);
482     }
483 
484     @Override
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)485     public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
486       String text = token.getTok().getOriginalText();
487       return state.withColumn(state.column + text.length());
488     }
489 
490     @Override
write(Output output)491     public void write(Output output) {
492       String text = token.getTok().getOriginalText();
493       output.append(text, range());
494     }
495 
496     @Override
toString()497     public String toString() {
498       return MoreObjects.toStringHelper(this)
499           .add("token", token)
500           .add("realOrImaginary", realOrImaginary)
501           .add("plusIndentCommentsBefore", plusIndentCommentsBefore)
502           .toString();
503     }
504   }
505 
506   /** A Leaf node in a {@link Doc} for a non-breaking space. */
507   static final class Space extends Doc implements Op {
508     private static final Space SPACE = new Space();
509 
Space()510     private Space() {}
511 
512     /**
513      * Factor method for {@code Space}.
514      *
515      * @return the new {@code Space}
516      */
make()517     static Space make() {
518       return SPACE;
519     }
520 
521     @Override
add(DocBuilder builder)522     public void add(DocBuilder builder) {
523       builder.add(this);
524     }
525 
526     @Override
computeWidth()527     float computeWidth() {
528       return 1.0F;
529     }
530 
531     @Override
computeFlat()532     String computeFlat() {
533       return " ";
534     }
535 
536     @Override
computeRange()537     Range<Integer> computeRange() {
538       return EMPTY_RANGE;
539     }
540 
541     @Override
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)542     public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
543       return state.withColumn(state.column + 1);
544     }
545 
546     @Override
write(Output output)547     public void write(Output output) {
548       output.append(" ", range());
549     }
550 
551     @Override
toString()552     public String toString() {
553       return MoreObjects.toStringHelper(this).toString();
554     }
555   }
556 
557   /** A leaf node in a {@link Doc} for an optional break. */
558   public static final class Break extends Doc implements Op {
559     private final FillMode fillMode;
560     private final String flat;
561     private final Indent plusIndent;
562     private final Optional<BreakTag> optTag;
563 
Break(FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag)564     private Break(FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) {
565       this.fillMode = fillMode;
566       this.flat = flat;
567       this.plusIndent = plusIndent;
568       this.optTag = optTag;
569     }
570 
571     /**
572      * Make a {@code Break}.
573      *
574      * @param fillMode the {@link FillMode}
575      * @param flat the text when not broken
576      * @param plusIndent extra indent if taken
577      * @return the new {@code Break}
578      */
make(FillMode fillMode, String flat, Indent plusIndent)579     public static Break make(FillMode fillMode, String flat, Indent plusIndent) {
580       return new Break(fillMode, flat, plusIndent, /* optTag= */ Optional.empty());
581     }
582 
583     /**
584      * Make a {@code Break}.
585      *
586      * @param fillMode the {@link FillMode}
587      * @param flat the text when not broken
588      * @param plusIndent extra indent if taken
589      * @param optTag an optional tag for remembering whether the break was taken
590      * @return the new {@code Break}
591      */
make( FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag)592     public static Break make(
593         FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) {
594       return new Break(fillMode, flat, plusIndent, optTag);
595     }
596 
597     /**
598      * Make a forced {@code Break}.
599      *
600      * @return the new forced {@code Break}
601      */
makeForced()602     public static Break makeForced() {
603       return make(FillMode.FORCED, "", Indent.Const.ZERO);
604     }
605 
606     /**
607      * Return the {@code Break}'s extra indent.
608      *
609      * @return the extra indent
610      */
getPlusIndent()611     int getPlusIndent() {
612       return plusIndent.eval();
613     }
614 
615     /**
616      * Is the {@code Break} forced?
617      *
618      * @return whether the {@code Break} is forced
619      */
isForced()620     boolean isForced() {
621       return fillMode == FillMode.FORCED;
622     }
623 
624     @Override
add(DocBuilder builder)625     public void add(DocBuilder builder) {
626       builder.breakDoc(this);
627     }
628 
629     @Override
computeWidth()630     float computeWidth() {
631       return isForced() ? Float.POSITIVE_INFINITY : (float) flat.length();
632     }
633 
634     @Override
computeFlat()635     String computeFlat() {
636       return flat;
637     }
638 
639     @Override
computeRange()640     Range<Integer> computeRange() {
641       return EMPTY_RANGE;
642     }
643 
644     /** Was this break taken? */
645     boolean broken;
646 
647     /** New indent after this break. */
648     int newIndent;
649 
computeBreaks(State state, int lastIndent, boolean broken)650     public State computeBreaks(State state, int lastIndent, boolean broken) {
651       if (optTag.isPresent()) {
652         optTag.get().recordBroken(broken);
653       }
654 
655       if (broken) {
656         this.broken = true;
657         this.newIndent = max(lastIndent + plusIndent.eval(), 0);
658         return state.withColumn(newIndent);
659       } else {
660         this.broken = false;
661         this.newIndent = -1;
662         return state.withColumn(state.column + flat.length());
663       }
664     }
665 
666     @Override
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)667     public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
668       // Updating the state for {@link Break}s requires deciding if the break
669       // should be taken.
670       // TODO(cushon): this hierarchy is wrong, create a separate interface
671       // for unbreakable Docs?
672       throw new UnsupportedOperationException("Did you mean computeBreaks(State, int, boolean)?");
673     }
674 
675     @Override
write(Output output)676     public void write(Output output) {
677       if (broken) {
678         output.append("\n", EMPTY_RANGE);
679         output.indent(newIndent);
680       } else {
681         output.append(flat, range());
682       }
683     }
684 
685     @Override
toString()686     public String toString() {
687       return MoreObjects.toStringHelper(this)
688           .add("fillMode", fillMode)
689           .add("flat", flat)
690           .add("plusIndent", plusIndent)
691           .add("optTag", optTag)
692           .toString();
693     }
694   }
695 
696   /** A leaf node in a {@link Doc} for a non-token. */
697   static final class Tok extends Doc implements Op {
698     private final Input.Tok tok;
699 
Tok(Input.Tok tok)700     private Tok(Input.Tok tok) {
701       this.tok = tok;
702     }
703 
704     /**
705      * Factory method for a {@code Tok}.
706      *
707      * @param tok the {@link Input.Tok} to wrap
708      * @return the new {@code Tok}
709      */
make(Input.Tok tok)710     static Tok make(Input.Tok tok) {
711       return new Tok(tok);
712     }
713 
714     @Override
add(DocBuilder builder)715     public void add(DocBuilder builder) {
716       builder.add(this);
717     }
718 
719     @Override
computeWidth()720     float computeWidth() {
721       int idx = Newlines.firstBreak(tok.getOriginalText());
722       // only count the first line of multi-line block comments
723       if (tok.isComment()) {
724         if (idx > 0) {
725           return idx;
726         } else if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) {
727           // Account for line comments with missing spaces, see computeFlat.
728           return tok.length() + 1;
729         } else {
730           return tok.length();
731         }
732       }
733       return idx != -1 ? Float.POSITIVE_INFINITY : (float) tok.length();
734     }
735 
736     @Override
computeFlat()737     String computeFlat() {
738       // TODO(cushon): commentsHelper.rewrite doesn't get called for spans that fit in a single
739       // line. That's fine for multi-line comment reflowing, but problematic for adding missing
740       // spaces in line comments.
741       if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) {
742         return "// " + tok.getOriginalText().substring("//".length());
743       }
744       return tok.getOriginalText();
745     }
746 
747     @Override
computeRange()748     Range<Integer> computeRange() {
749       return Range.singleton(tok.getIndex()).canonical(INTEGERS);
750     }
751 
752     String text;
753 
754     @Override
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)755     public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
756       text = commentsHelper.rewrite(tok, maxWidth, state.column);
757       int firstLineLength = text.length() - Iterators.getLast(Newlines.lineOffsetIterator(text));
758       return state.withColumn(state.column + firstLineLength);
759     }
760 
761     @Override
write(Output output)762     public void write(Output output) {
763       output.append(text, range());
764     }
765 
766     @Override
toString()767     public String toString() {
768       return MoreObjects.toStringHelper(this).add("tok", tok).toString();
769     }
770   }
771 }
772