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