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                     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                 }
512                 break;
513 
514             }
515             if (!modifiers.isEmpty()) {
516                 writer.print(modifiers.stream()
517                              .map(Modifier::toString)
518                              .collect(Collectors.joining(" ", "", " ")));
519             }
520         }
521 
522         private void printFormalTypeParameters(Parameterizable e,
523                                                boolean pad) {
524             List<? extends TypeParameterElement> typeParams = e.getTypeParameters();
525             if (!typeParams.isEmpty()) {
526                 writer.print(typeParams.stream()
527                              .map(tpe -> annotationsToString(tpe) + tpe.toString())
528                              .collect(Collectors.joining(", ", "<", ">")));
529                 if (pad)
530                     writer.print(" ");
531             }
532         }
533 
534         private String annotationsToString(Element e) {
535             List<? extends AnnotationMirror> annotations = e.getAnnotationMirrors();
536             return annotations.isEmpty() ?
537                 "" :
538                 annotations.stream()
539                 .map(AnnotationMirror::toString)
540                 .collect(Collectors.joining(" ", "", " "));
541         }
542 
543         private void printAnnotations(Element e) {
544             List<? extends AnnotationMirror> annots = e.getAnnotationMirrors();
545             for(AnnotationMirror annotationMirror : annots) {
546                 // Handle compiler-generated container annotations specially
547                 if (!printedContainerAnnotation(e, annotationMirror)) {
548                     indent();
549                     writer.println(annotationMirror);
550                 }
551             }
552         }
553 
554         private boolean printedContainerAnnotation(Element e,
555                                                    AnnotationMirror annotationMirror) {
556             /*
557              * If the annotation mirror is marked as mandated and
558              * looks like a container annotation, elide printing the
559              * container and just print the wrapped contents.
560              */
561             if (elementUtils.getOrigin(e, annotationMirror) == Elements.Origin.MANDATED) {
562                 // From JLS Chapter 9, an annotation interface AC is a
563                 // containing annotation interface of A if AC declares
564                 // a value() method whose return type is A[] and any
565                 // methods declared by AC other than value() have a
566                 // default value. As an implementation choice, if more
567                 // than one annotation element is found on the outer
568                 // annotation, in other words, something besides a
569                 // "value" method, the annotation will not be treated
570                 // as a wrapper for the purposes of printing. These
571                 // checks are intended to preserve correctness in the
572                 // face of some other kind of annotation being marked
573                 // as mandated.
574 
575                 var entries = annotationMirror.getElementValues().entrySet();
576                 if (entries.size() == 1) {
577                     var annotationType = annotationMirror.getAnnotationType();
578                     var annotationTypeAsElement = annotationType.asElement();
579 
580                     var entry = entries.iterator().next();
581                     var annotationElements = entry.getValue();
582 
583                     // Check that the annotation type declaration has
584                     // a single method named "value" and that it
585                     // returns an array. A stricter check would be
586                     // that it is an array of an annotation type and
587                     // that annotation type in turn was repeatable.
588                     if (annotationTypeAsElement.getKind() == ElementKind.ANNOTATION_TYPE) {
589                         var annotationMethods =
590                             ElementFilter.methodsIn(annotationTypeAsElement.getEnclosedElements());
591                         if (annotationMethods.size() == 1) {
592                             var valueMethod = annotationMethods.get(0);
593                             var returnType = valueMethod.getReturnType();
594 
595                             if ("value".equals(valueMethod.getSimpleName().toString()) &&
596                                 returnType.getKind() == TypeKind.ARRAY) {
597                                 // Use annotation value visitor that
598                                 // returns a boolean if it prints out
599                                 // contained annotations as expected
600                                 // and false otherwise
601 
602                                 return (new SimpleAnnotationValueVisitor14<Boolean, Void>(false) {
603                                     @Override
604                                     public Boolean visitArray(List<? extends AnnotationValue> vals, Void p) {
605                                         if (vals.size() < 2) {
606                                             return false;
607                                         } else {
608                                             for (var annotValue: vals) {
609                                                 indent();
610                                                 writer.println(annotValue.toString());
611                                             }
612                                             return true;
613                                         }
614                                     }
615                                 }).visit(annotationElements);
616                             }
617                         }
618                     }
619                 }
620             }
621             return false;
622         }
623 
624         // TODO: Refactor
625         private void printParameters(ExecutableElement e) {
626             List<? extends VariableElement> parameters = e.getParameters();
627             int size = parameters.size();
628 
629             switch (size) {
630             case 0:
631                 break;
632 
633             case 1:
634                 for(VariableElement parameter: parameters) {
635                     printModifiers(parameter);
636 
637                     if (e.isVarArgs() ) {
638                         TypeMirror tm = parameter.asType();
639                         if (tm.getKind() != TypeKind.ARRAY)
640                             throw new AssertionError("Var-args parameter is not an array type: " + tm);
641                         writer.print((ArrayType.class.cast(tm)).getComponentType() );
642                         writer.print("...");
643                     } else
644                         writer.print(parameter.asType());
645                     writer.print(" " + parameter.getSimpleName());
646                 }
647                 break;
648 
649             default:
650                 {
651                     int i = 1;
652                     for(VariableElement parameter: parameters) {
653                         if (i == 2)
654                             indentation++;
655 
656                         if (i > 1)
657                             indent();
658 
659                         printModifiers(parameter);
660 
661                         if (i == size && e.isVarArgs() ) {
662                             TypeMirror tm = parameter.asType();
663                             if (tm.getKind() != TypeKind.ARRAY)
664                                 throw new AssertionError("Var-args parameter is not an array type: " + tm);
665                                     writer.print((ArrayType.class.cast(tm)).getComponentType() );
666 
667                             writer.print("...");
668                         } else
669                             writer.print(parameter.asType());
670                         writer.print(" " + parameter.getSimpleName());
671 
672                         if (i < size)
673                             writer.println(",");
674 
675                         i++;
676                     }
677 
678                     if (parameters.size() >= 2)
679                         indentation--;
680                 }
681                 break;
682             }
683         }
684 
685         private void printInterfaces(TypeElement e) {
686             ElementKind kind = e.getKind();
687 
688             if(kind != ANNOTATION_TYPE) {
689                 List<? extends TypeMirror> interfaces = e.getInterfaces();
690                 if (!interfaces.isEmpty()) {
691                     writer.print((kind.isClass() ? " implements " : " extends "));
692                     writer.print(interfaces.stream()
693                                  .map(TypeMirror::toString)
694                                  .collect(Collectors.joining(", ")));
695                 }
696             }
697         }
698 
699         private void printPermittedSubclasses(TypeElement e) {
700             if (e.getKind() == ENUM) {
701                 // any permitted classes on an enum are anonymous
702                 // classes for enum bodies, elide.
703                 return;
704             }
705 
706             List<? extends TypeMirror> subtypes = e.getPermittedSubclasses();
707             if (!subtypes.isEmpty()) { // could remove this check with more complicated joining call
708                 writer.print(" permits ");
709                 writer.print(subtypes
710                              .stream()
711                              .map(subtype -> subtype.toString())
712                              .collect(Collectors.joining(", ")));
713             }
714         }
715 
716         private void printThrows(ExecutableElement e) {
717             List<? extends TypeMirror> thrownTypes = e.getThrownTypes();
718             final int size = thrownTypes.size();
719             if (size != 0) {
720                 writer.print(" throws");
721 
722                 int i = 1;
723                 for(TypeMirror thrownType: thrownTypes) {
724                     if (i == 1)
725                         writer.print(" ");
726 
727                     if (i == 2)
728                         indentation++;
729 
730                     if (i >= 2)
731                         indent();
732 
733                     writer.print(thrownType);
734 
735                     if (i != size)
736                         writer.println(", ");
737 
738                     i++;
739                 }
740 
741                 if (size >= 2)
742                     indentation--;
743             }
744         }
745 
746         private static final String [] spaces = {
747             "",
748             "  ",
749             "    ",
750             "      ",
751             "        ",
752             "          ",
753             "            ",
754             "              ",
755             "                ",
756             "                  ",
757             "                    "
758         };
759 
760         private void indent() {
761             int indentation = this.indentation;
762             if (indentation < 0)
763                 return;
764             final int maxIndex = spaces.length - 1;
765 
766             while (indentation > maxIndex) {
767                 writer.print(spaces[maxIndex]);
768                 indentation -= maxIndex;
769             }
770             writer.print(spaces[indentation]);
771         }
772 
773     }
774 }