1 /* 2 * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.javac.processing; 27 28 import javax.annotation.processing.*; 29 import javax.lang.model.*; 30 import javax.lang.model.element.*; 31 import static javax.lang.model.element.ElementKind.*; 32 import static javax.lang.model.element.NestingKind.*; 33 import static javax.lang.model.element.ModuleElement.*; 34 import javax.lang.model.type.*; 35 import javax.lang.model.util.*; 36 37 import java.io.PrintWriter; 38 import java.io.Writer; 39 import java.util.*; 40 import java.util.stream.Collectors; 41 42 43 import com.sun.tools.javac.util.DefinedBy; 44 import com.sun.tools.javac.util.DefinedBy.Api; 45 import com.sun.tools.javac.util.StringUtils; 46 47 /** 48 * A processor which prints out elements. Used to implement the 49 * -Xprint option; the included visitor class is used to implement 50 * Elements.printElements. 51 * 52 * <p><b>This is NOT part of any supported API. 53 * If you write code that depends on this, you do so at your own risk. 54 * This code and its internal interfaces are subject to change or 55 * deletion without notice.</b> 56 */ 57 @SupportedAnnotationTypes("*") 58 @SupportedSourceVersion(SourceVersion.RELEASE_25) 59 public class PrintingProcessor extends AbstractProcessor { 60 PrintWriter writer; 61 62 public PrintingProcessor() { 63 super(); 64 writer = new PrintWriter(System.out); 65 } 66 67 public void setWriter(Writer w) { 68 writer = new PrintWriter(w); 69 } 70 71 @Override @DefinedBy(Api.ANNOTATION_PROCESSING) 72 public boolean process(Set<? extends TypeElement> tes, 73 RoundEnvironment renv) { 74 75 for(Element element : renv.getRootElements()) { 76 print(element); 77 } 78 79 // Just print the elements, nothing more to do. 80 return true; 81 } 82 83 void print(Element element) { 84 new PrintingElementVisitor(writer, processingEnv.getElementUtils()). 85 visit(element).flush(); 86 } 87 88 /** 89 * Used for the -Xprint option and called by Elements.printElements 90 */ 91 public static class PrintingElementVisitor 92 extends SimpleElementVisitor14<PrintingElementVisitor, Boolean> { 93 int indentation; // Indentation level; 94 final PrintWriter writer; 95 final Elements elementUtils; 96 97 public PrintingElementVisitor(Writer w, Elements elementUtils) { 98 super(); 99 this.writer = new PrintWriter(w); 100 this.elementUtils = elementUtils; 101 indentation = 0; 102 } 103 104 @Override @DefinedBy(Api.LANGUAGE_MODEL) 105 protected PrintingElementVisitor defaultAction(Element e, Boolean newLine) { 106 if (newLine != null && newLine) 107 writer.println(); 108 printDocComment(e); 109 printModifiers(e); 110 return this; 111 } 112 113 @Override @DefinedBy(Api.LANGUAGE_MODEL) 114 public PrintingElementVisitor visitRecordComponent(RecordComponentElement e, Boolean p) { 115 // Do nothing; printing of component information done by 116 // printing the record type itself 117 return this; 118 } 119 120 @Override @DefinedBy(Api.LANGUAGE_MODEL) 121 public PrintingElementVisitor visitExecutable(ExecutableElement e, Boolean p) { 122 ElementKind kind = e.getKind(); 123 124 if (kind != STATIC_INIT && 125 kind != INSTANCE_INIT) { 126 Element enclosing = e.getEnclosingElement(); 127 128 // Don't print out the constructor of an anonymous class 129 if (kind == CONSTRUCTOR && 130 enclosing != null && 131 (NestingKind.ANONYMOUS == 132 // Use an anonymous class to determine anonymity! 133 (new SimpleElementVisitor14<NestingKind, Void>() { 134 @Override @DefinedBy(Api.LANGUAGE_MODEL) 135 public NestingKind visitType(TypeElement e, Void p) { 136 return e.getNestingKind(); 137 } 138 }).visit(enclosing)) ) { 139 return this; 140 } 141 142 defaultAction(e, true); 143 printFormalTypeParameters(e, true); 144 145 switch(kind) { 146 case CONSTRUCTOR: 147 // Print out simple name of the class 148 writer.print(e.getEnclosingElement().getSimpleName()); 149 break; 150 151 case METHOD: 152 writer.print(e.getReturnType().toString()); 153 writer.print(" "); 154 writer.print(e.getSimpleName().toString()); 155 break; 156 } 157 158 if (elementUtils.isCompactConstructor(e)) { 159 // A record's compact constructor by definition 160 // lacks source-explicit parameters and lacks a 161 // throws clause. 162 writer.print(" {} /* compact constructor */ "); 163 } else { 164 writer.print("("); 165 printParameters(e); 166 writer.print(")"); 167 168 // Display any default values for an annotation 169 // interface element 170 AnnotationValue defaultValue = e.getDefaultValue(); 171 if (defaultValue != null) 172 writer.print(" default " + defaultValue); 173 174 printThrows(e); 175 } 176 177 writer.println(";"); 178 } 179 return this; 180 } 181 182 183 @Override @DefinedBy(Api.LANGUAGE_MODEL) 184 public PrintingElementVisitor visitType(TypeElement e, Boolean p) { 185 ElementKind kind = e.getKind(); 186 NestingKind nestingKind = e.getNestingKind(); 187 188 if (NestingKind.ANONYMOUS == nestingKind) { 189 // Print nothing for an anonymous class used for an 190 // enum constant body. 191 TypeMirror supertype = e.getSuperclass(); 192 if (supertype.getKind() != TypeKind.NONE) { 193 TypeElement superClass = (TypeElement)(((DeclaredType)supertype).asElement()); 194 if (superClass.getKind() == ENUM) { 195 return this; 196 } 197 } 198 199 // Print out an anonymous class in the style of a 200 // class instance creation expression rather than a 201 // class declaration. 202 writer.print("new "); 203 204 // If the anonymous class implements an interface 205 // print that name, otherwise print the superclass. 206 List<? extends TypeMirror> interfaces = e.getInterfaces(); 207 if (!interfaces.isEmpty()) 208 writer.print(interfaces.get(0)); 209 else 210 writer.print(e.getSuperclass()); 211 212 writer.print("("); 213 // Anonymous classes that implement an interface can't 214 // have any constructor arguments. 215 if (interfaces.isEmpty()) { 216 // Print out the parameter list from the sole 217 // constructor. For now, don't try to elide any 218 // synthetic parameters by determining if the 219 // anonymous class is in a static context, etc. 220 List<? extends ExecutableElement> constructors = 221 ElementFilter.constructorsIn(e.getEnclosedElements()); 222 223 if (!constructors.isEmpty()) 224 printParameters(constructors.get(0)); 225 } 226 writer.print(")"); 227 } else { 228 if (nestingKind == TOP_LEVEL) { 229 PackageElement pkg = elementUtils.getPackageOf(e); 230 if (!pkg.isUnnamed()) 231 writer.print("package " + pkg.getQualifiedName() + ";\n"); 232 } 233 234 defaultAction(e, true); 235 236 switch(kind) { 237 case ANNOTATION_TYPE: 238 writer.print("@interface"); 239 break; 240 default: 241 writer.print(StringUtils.toLowerCase(kind.toString())); 242 } 243 writer.print(" "); 244 writer.print(e.getSimpleName()); 245 246 printFormalTypeParameters(e, false); 247 248 if (kind == RECORD) { 249 // Print out record components 250 writer.print("("); 251 writer.print(e.getRecordComponents() 252 .stream() 253 .map(recordDes -> annotationsToString(recordDes) + recordDes.asType().toString() + " " + recordDes.getSimpleName()) 254 .collect(Collectors.joining(", "))); 255 writer.print(")"); 256 } 257 258 // Print superclass information if informative 259 if (kind == CLASS) { 260 TypeMirror supertype = e.getSuperclass(); 261 if (supertype.getKind() != TypeKind.NONE) { 262 TypeElement e2 = (TypeElement) 263 ((DeclaredType) supertype).asElement(); 264 if (e2.getSuperclass().getKind() != TypeKind.NONE) 265 writer.print(" extends " + supertype); 266 } 267 } 268 269 printInterfaces(e); 270 printPermittedSubclasses(e); 271 } 272 writer.println(" {"); 273 indentation++; 274 275 if (kind == ENUM) { 276 List<Element> enclosedElements = new ArrayList<>(e.getEnclosedElements()); 277 // Handle any enum constants specially before other entities. 278 List<Element> enumConstants = new ArrayList<>(); 279 for(Element element : enclosedElements) { 280 if (element.getKind() == ENUM_CONSTANT) 281 enumConstants.add(element); 282 } 283 if (!enumConstants.isEmpty()) { 284 int i; 285 for(i = 0; i < enumConstants.size()-1; i++) { 286 this.visit(enumConstants.get(i), true); 287 writer.print(","); 288 } 289 this.visit(enumConstants.get(i), true); 290 writer.println(";\n"); 291 292 enclosedElements.removeAll(enumConstants); 293 } 294 295 for(Element element : enclosedElements) 296 this.visit(element); 297 } else { 298 for(Element element : 299 (kind != RECORD ? 300 e.getEnclosedElements() : 301 e.getEnclosedElements() 302 .stream() 303 .filter(elt -> elementUtils.getOrigin(elt) == Elements.Origin.EXPLICIT ) 304 .toList() ) ) 305 this.visit(element); 306 } 307 308 indentation--; 309 indent(); 310 writer.println("}"); 311 return this; 312 } 313 314 @Override @DefinedBy(Api.LANGUAGE_MODEL) 315 public PrintingElementVisitor visitVariable(VariableElement e, Boolean newLine) { 316 ElementKind kind = e.getKind(); 317 defaultAction(e, newLine); 318 319 if (kind == ENUM_CONSTANT) 320 writer.print(e.getSimpleName()); 321 else { 322 writer.print(e.asType().toString() + " " + (e.getSimpleName().isEmpty() ? "_" : e.getSimpleName())); 323 Object constantValue = e.getConstantValue(); 324 if (constantValue != null) { 325 writer.print(" = "); 326 writer.print(elementUtils.getConstantExpression(constantValue)); 327 } 328 writer.println(";"); 329 } 330 return this; 331 } 332 333 @Override @DefinedBy(Api.LANGUAGE_MODEL) 334 public PrintingElementVisitor visitTypeParameter(TypeParameterElement e, Boolean p) { 335 writer.print(e.getSimpleName()); 336 return this; 337 } 338 339 // Should we do more here? 340 @Override @DefinedBy(Api.LANGUAGE_MODEL) 341 public PrintingElementVisitor visitPackage(PackageElement e, Boolean p) { 342 defaultAction(e, false); 343 if (!e.isUnnamed()) 344 writer.println("package " + e.getQualifiedName() + ";"); 345 else 346 writer.println("// Unnamed package"); 347 return this; 348 } 349 350 @Override @DefinedBy(Api.LANGUAGE_MODEL) 351 public PrintingElementVisitor visitModule(ModuleElement e, Boolean p) { 352 defaultAction(e, false); 353 354 if (!e.isUnnamed()) { 355 if (e.isOpen()) { 356 writer.print("open "); 357 } 358 writer.println("module " + e.getQualifiedName() + " {"); 359 indentation++; 360 for (ModuleElement.Directive directive : e.getDirectives()) { 361 printDirective(directive); 362 } 363 indentation--; 364 writer.println("}"); 365 } else 366 writer.println("// Unnamed module"); // Should we do more here? 367 return this; 368 } 369 370 private void printDirective(ModuleElement.Directive directive) { 371 indent(); 372 (new PrintDirective(writer)).visit(directive); 373 writer.println(";"); 374 } 375 376 private static class PrintDirective implements ModuleElement.DirectiveVisitor<Void, Void> { 377 private final PrintWriter writer; 378 379 PrintDirective(PrintWriter writer) { 380 this.writer = writer; 381 } 382 383 @Override @DefinedBy(Api.LANGUAGE_MODEL) 384 public Void visitExports(ExportsDirective d, Void p) { 385 // "exports package-name [to module-name-list]" 386 writer.print("exports "); 387 writer.print(d.getPackage().getQualifiedName()); 388 printModuleList(d.getTargetModules()); 389 return null; 390 } 391 392 @Override @DefinedBy(Api.LANGUAGE_MODEL) 393 public Void visitOpens(OpensDirective d, Void p) { 394 // opens package-name [to module-name-list] 395 writer.print("opens "); 396 writer.print(d.getPackage().getQualifiedName()); 397 printModuleList(d.getTargetModules()); 398 return null; 399 } 400 401 @Override @DefinedBy(Api.LANGUAGE_MODEL) 402 public Void visitProvides(ProvidesDirective d, Void p) { 403 // provides service-name with implementation-name 404 writer.print("provides "); 405 writer.print(d.getService().getQualifiedName()); 406 writer.print(" with "); 407 printNameableList(d.getImplementations()); 408 return null; 409 } 410 411 @Override @DefinedBy(Api.LANGUAGE_MODEL) 412 public Void visitRequires(RequiresDirective d, Void p) { 413 // requires (static|transitive)* module-name 414 writer.print("requires "); 415 if (d.isStatic()) 416 writer.print("static "); 417 if (d.isTransitive()) 418 writer.print("transitive "); 419 writer.print(d.getDependency().getQualifiedName()); 420 return null; 421 } 422 423 @Override @DefinedBy(Api.LANGUAGE_MODEL) 424 public Void visitUses(UsesDirective d, Void p) { 425 // uses service-name 426 writer.print("uses "); 427 writer.print(d.getService().getQualifiedName()); 428 return null; 429 } 430 431 private void printModuleList(List<? extends ModuleElement> modules) { 432 if (modules != null) { 433 writer.print(" to "); 434 printNameableList(modules); 435 } 436 } 437 438 private void printNameableList(List<? extends QualifiedNameable> nameables) { 439 writer.print(nameables.stream(). 440 map(QualifiedNameable::getQualifiedName). 441 collect(Collectors.joining(", "))); 442 } 443 } 444 445 public void flush() { 446 writer.flush(); 447 } 448 449 private void printDocComment(Element e) { 450 String docComment = elementUtils.getDocComment(e); 451 452 if (docComment != null) { 453 // Break comment into lines 454 java.util.StringTokenizer st = new StringTokenizer(docComment, 455 "\n\r"); 456 indent(); 457 writer.println("/**"); 458 459 while(st.hasMoreTokens()) { 460 indent(); 461 writer.print(" *"); 462 writer.println(st.nextToken()); 463 } 464 465 indent(); 466 writer.println(" */"); 467 } 468 } 469 470 private void printModifiers(Element e) { 471 ElementKind kind = e.getKind(); 472 if (kind == PARAMETER || kind == RECORD_COMPONENT) { 473 // Print annotation inline 474 writer.print(annotationsToString(e)); 475 } else { 476 printAnnotations(e); 477 indent(); 478 } 479 480 if (kind == ENUM_CONSTANT || kind == RECORD_COMPONENT) 481 return; 482 483 Set<Modifier> modifiers = new LinkedHashSet<>(); 484 modifiers.addAll(e.getModifiers()); 485 486 switch (kind) { 487 case ANNOTATION_TYPE: 488 case INTERFACE: 489 modifiers.remove(Modifier.ABSTRACT); 490 break; 491 492 case ENUM: 493 modifiers.remove(Modifier.FINAL); 494 modifiers.remove(Modifier.ABSTRACT); 495 modifiers.remove(Modifier.SEALED); 496 break; 497 498 case RECORD: 499 modifiers.remove(Modifier.FINAL); 500 break; 501 502 case METHOD: 503 case FIELD: 504 Element enclosingElement = e.getEnclosingElement(); 505 if (enclosingElement != null) { 506 if (enclosingElement.getKind().isInterface()) { 507 modifiers.remove(Modifier.PUBLIC); 508 modifiers.remove(Modifier.ABSTRACT); // only for methods 509 modifiers.remove(Modifier.STATIC); // only for fields 510 modifiers.remove(Modifier.FINAL); // only for fields 511 } else if (enclosingElement.getKind() == RECORD) { 512 modifiers.remove(Modifier.STRICTFP); 513 } 514 } 515 break; 516 517 } 518 if (!modifiers.isEmpty()) { 519 writer.print(modifiers.stream() 520 .map(Modifier::toString) 521 .collect(Collectors.joining(" ", "", " "))); 522 } 523 } 524 525 private void printFormalTypeParameters(Parameterizable e, 526 boolean pad) { 527 List<? extends TypeParameterElement> typeParams = e.getTypeParameters(); 528 if (!typeParams.isEmpty()) { 529 writer.print(typeParams.stream() 530 .map(tpe -> annotationsToString(tpe) + tpe.toString()) 531 .collect(Collectors.joining(", ", "<", ">"))); 532 if (pad) 533 writer.print(" "); 534 } 535 } 536 537 private String annotationsToString(Element e) { 538 List<? extends AnnotationMirror> annotations = e.getAnnotationMirrors(); 539 return annotations.isEmpty() ? 540 "" : 541 annotations.stream() 542 .map(AnnotationMirror::toString) 543 .collect(Collectors.joining(" ", "", " ")); 544 } 545 546 private void printAnnotations(Element e) { 547 List<? extends AnnotationMirror> annots = e.getAnnotationMirrors(); 548 for(AnnotationMirror annotationMirror : annots) { 549 // Handle compiler-generated container annotations specially 550 if (!printedContainerAnnotation(e, annotationMirror)) { 551 indent(); 552 writer.println(annotationMirror); 553 } 554 } 555 } 556 557 private boolean printedContainerAnnotation(Element e, 558 AnnotationMirror annotationMirror) { 559 /* 560 * If the annotation mirror is marked as mandated and 561 * looks like a container annotation, elide printing the 562 * container and just print the wrapped contents. 563 */ 564 if (elementUtils.getOrigin(e, annotationMirror) == Elements.Origin.MANDATED) { 565 // From JLS Chapter 9, an annotation interface AC is a 566 // containing annotation interface of A if AC declares 567 // a value() method whose return type is A[] and any 568 // methods declared by AC other than value() have a 569 // default value. As an implementation choice, if more 570 // than one annotation element is found on the outer 571 // annotation, in other words, something besides a 572 // "value" method, the annotation will not be treated 573 // as a wrapper for the purposes of printing. These 574 // checks are intended to preserve correctness in the 575 // face of some other kind of annotation being marked 576 // as mandated. 577 578 var entries = annotationMirror.getElementValues().entrySet(); 579 if (entries.size() == 1) { 580 var annotationType = annotationMirror.getAnnotationType(); 581 var annotationTypeAsElement = annotationType.asElement(); 582 583 var entry = entries.iterator().next(); 584 var annotationElements = entry.getValue(); 585 586 // Check that the annotation type declaration has 587 // a single method named "value" and that it 588 // returns an array. A stricter check would be 589 // that it is an array of an annotation type and 590 // that annotation type in turn was repeatable. 591 if (annotationTypeAsElement.getKind() == ElementKind.ANNOTATION_TYPE) { 592 var annotationMethods = 593 ElementFilter.methodsIn(annotationTypeAsElement.getEnclosedElements()); 594 if (annotationMethods.size() == 1) { 595 var valueMethod = annotationMethods.get(0); 596 var returnType = valueMethod.getReturnType(); 597 598 if ("value".equals(valueMethod.getSimpleName().toString()) && 599 returnType.getKind() == TypeKind.ARRAY) { 600 // Use annotation value visitor that 601 // returns a boolean if it prints out 602 // contained annotations as expected 603 // and false otherwise 604 605 return (new SimpleAnnotationValueVisitor14<Boolean, Void>(false) { 606 @Override 607 public Boolean visitArray(List<? extends AnnotationValue> vals, Void p) { 608 if (vals.size() < 2) { 609 return false; 610 } else { 611 for (var annotValue: vals) { 612 indent(); 613 writer.println(annotValue.toString()); 614 } 615 return true; 616 } 617 } 618 }).visit(annotationElements); 619 } 620 } 621 } 622 } 623 } 624 return false; 625 } 626 627 // TODO: Refactor 628 private void printParameters(ExecutableElement e) { 629 List<? extends VariableElement> parameters = e.getParameters(); 630 int size = parameters.size(); 631 632 switch (size) { 633 case 0: 634 break; 635 636 case 1: 637 for(VariableElement parameter: parameters) { 638 printModifiers(parameter); 639 640 if (e.isVarArgs() ) { 641 TypeMirror tm = parameter.asType(); 642 if (tm.getKind() != TypeKind.ARRAY) 643 throw new AssertionError("Var-args parameter is not an array type: " + tm); 644 writer.print((ArrayType.class.cast(tm)).getComponentType() ); 645 writer.print("..."); 646 } else 647 writer.print(parameter.asType()); 648 writer.print(" " + parameter.getSimpleName()); 649 } 650 break; 651 652 default: 653 { 654 int i = 1; 655 for(VariableElement parameter: parameters) { 656 if (i == 2) 657 indentation++; 658 659 if (i > 1) 660 indent(); 661 662 printModifiers(parameter); 663 664 if (i == size && e.isVarArgs() ) { 665 TypeMirror tm = parameter.asType(); 666 if (tm.getKind() != TypeKind.ARRAY) 667 throw new AssertionError("Var-args parameter is not an array type: " + tm); 668 writer.print((ArrayType.class.cast(tm)).getComponentType() ); 669 670 writer.print("..."); 671 } else 672 writer.print(parameter.asType()); 673 writer.print(" " + parameter.getSimpleName()); 674 675 if (i < size) 676 writer.println(","); 677 678 i++; 679 } 680 681 if (parameters.size() >= 2) 682 indentation--; 683 } 684 break; 685 } 686 } 687 688 private void printInterfaces(TypeElement e) { 689 ElementKind kind = e.getKind(); 690 691 if(kind != ANNOTATION_TYPE) { 692 List<? extends TypeMirror> interfaces = e.getInterfaces(); 693 if (!interfaces.isEmpty()) { 694 writer.print((kind.isClass() ? " implements " : " extends ")); 695 writer.print(interfaces.stream() 696 .map(TypeMirror::toString) 697 .collect(Collectors.joining(", "))); 698 } 699 } 700 } 701 702 private void printPermittedSubclasses(TypeElement e) { 703 if (e.getKind() == ENUM) { 704 // any permitted classes on an enum are anonymous 705 // classes for enum bodies, elide. 706 return; 707 } 708 709 List<? extends TypeMirror> subtypes = e.getPermittedSubclasses(); 710 if (!subtypes.isEmpty()) { // could remove this check with more complicated joining call 711 writer.print(" permits "); 712 writer.print(subtypes 713 .stream() 714 .map(subtype -> subtype.toString()) 715 .collect(Collectors.joining(", "))); 716 } 717 } 718 719 private void printThrows(ExecutableElement e) { 720 List<? extends TypeMirror> thrownTypes = e.getThrownTypes(); 721 final int size = thrownTypes.size(); 722 if (size != 0) { 723 writer.print(" throws"); 724 725 int i = 1; 726 for(TypeMirror thrownType: thrownTypes) { 727 if (i == 1) 728 writer.print(" "); 729 730 if (i == 2) 731 indentation++; 732 733 if (i >= 2) 734 indent(); 735 736 writer.print(thrownType); 737 738 if (i != size) 739 writer.println(", "); 740 741 i++; 742 } 743 744 if (size >= 2) 745 indentation--; 746 } 747 } 748 749 private static final String [] spaces = { 750 "", 751 " ", 752 " ", 753 " ", 754 " ", 755 " ", 756 " ", 757 " ", 758 " ", 759 " ", 760 " " 761 }; 762 763 private void indent() { 764 int indentation = this.indentation; 765 if (indentation < 0) 766 return; 767 final int maxIndex = spaces.length - 1; 768 769 while (indentation > maxIndex) { 770 writer.print(spaces[maxIndex]); 771 indentation -= maxIndex; 772 } 773 writer.print(spaces[indentation]); 774 } 775 776 } 777 }