1 /*
  2  * Copyright (c) 2005, 2025, 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_26)
 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                         if (isImportantType(supertype))
263                             writer.print(" extends " + supertype);
264                     }
265                 }
266 
267                 printInterfaces(e);
268                 printPermittedSubclasses(e);
269             }
270             writer.println(" {");
271             indentation++;
272 
273             if (kind == ENUM) {
274                 List<Element> enclosedElements = new ArrayList<>(e.getEnclosedElements());
275                 // Handle any enum constants specially before other entities.
276                 List<Element> enumConstants = new ArrayList<>();
277                 for(Element element : enclosedElements) {
278                     if (element.getKind() == ENUM_CONSTANT)
279                         enumConstants.add(element);
280                 }
281                 if (!enumConstants.isEmpty()) {
282                     int i;
283                     for(i = 0; i < enumConstants.size()-1; i++) {
284                         this.visit(enumConstants.get(i), true);
285                         writer.print(",");
286                     }
287                     this.visit(enumConstants.get(i), true);
288                     writer.println(";\n");
289 
290                     enclosedElements.removeAll(enumConstants);
291                 }
292 
293                 for(Element element : enclosedElements)
294                     this.visit(element);
295             } else {
296                 for(Element element :
297                         (kind != RECORD ?
298                          e.getEnclosedElements() :
299                          e.getEnclosedElements()
300                          .stream()
301                          .filter(elt -> elementUtils.getOrigin(elt) == Elements.Origin.EXPLICIT )
302                          .toList() ) )
303                     this.visit(element);
304             }
305 
306             indentation--;
307             indent();
308             writer.println("}");
309             return this;
310         }
311 
312         @Override @DefinedBy(Api.LANGUAGE_MODEL)
313         public PrintingElementVisitor visitVariable(VariableElement e, Boolean newLine) {
314             ElementKind kind = e.getKind();
315             defaultAction(e, newLine);
316 
317             if (kind == ENUM_CONSTANT)
318                 writer.print(e.getSimpleName());
319             else {
320                 writer.print(e.asType().toString() + " " + (e.getSimpleName().isEmpty() ? "_" : e.getSimpleName()));
321                 Object constantValue  = e.getConstantValue();
322                 if (constantValue != null) {
323                     writer.print(" = ");
324                     writer.print(elementUtils.getConstantExpression(constantValue));
325                 }
326                 writer.println(";");
327             }
328             return this;
329         }
330 
331         @Override @DefinedBy(Api.LANGUAGE_MODEL)
332         public PrintingElementVisitor visitTypeParameter(TypeParameterElement e, Boolean p) {
333             writer.print(e.getSimpleName());
334             return this;
335         }
336 
337         // Should we do more here?
338         @Override @DefinedBy(Api.LANGUAGE_MODEL)
339         public PrintingElementVisitor visitPackage(PackageElement e, Boolean p) {
340             defaultAction(e, false);
341             if (!e.isUnnamed())
342                 writer.println("package " + e.getQualifiedName() + ";");
343             else
344                 writer.println("// Unnamed package");
345             return this;
346         }
347 
348         @Override @DefinedBy(Api.LANGUAGE_MODEL)
349         public PrintingElementVisitor visitModule(ModuleElement e, Boolean p) {
350             defaultAction(e, false);
351 
352             if (!e.isUnnamed()) {
353                 if (e.isOpen()) {
354                     writer.print("open ");
355                 }
356                 writer.println("module " + e.getQualifiedName() + " {");
357                 indentation++;
358                 for (ModuleElement.Directive directive : e.getDirectives()) {
359                     printDirective(directive);
360                 }
361                 indentation--;
362                 writer.println("}");
363             } else
364                 writer.println("// Unnamed module"); // Should we do more here?
365             return this;
366         }
367 
368         private void printDirective(ModuleElement.Directive directive) {
369             indent();
370             (new PrintDirective(writer)).visit(directive);
371             writer.println(";");
372         }
373 
374         private static class PrintDirective implements ModuleElement.DirectiveVisitor<Void, Void> {
375             private final PrintWriter writer;
376 
377             PrintDirective(PrintWriter writer) {
378                 this.writer = writer;
379             }
380 
381             @Override @DefinedBy(Api.LANGUAGE_MODEL)
382             public Void visitExports(ExportsDirective d, Void p) {
383                 // "exports package-name [to module-name-list]"
384                 writer.print("exports ");
385                 writer.print(d.getPackage().getQualifiedName());
386                 printModuleList(d.getTargetModules());
387                 return null;
388             }
389 
390             @Override @DefinedBy(Api.LANGUAGE_MODEL)
391             public Void visitOpens(OpensDirective d, Void p) {
392                 // opens package-name [to module-name-list]
393                 writer.print("opens ");
394                 writer.print(d.getPackage().getQualifiedName());
395                 printModuleList(d.getTargetModules());
396                 return null;
397             }
398 
399             @Override @DefinedBy(Api.LANGUAGE_MODEL)
400             public Void visitProvides(ProvidesDirective d, Void p) {
401                 // provides service-name with implementation-name
402                 writer.print("provides ");
403                 writer.print(d.getService().getQualifiedName());
404                 writer.print(" with ");
405                 printNameableList(d.getImplementations());
406                 return null;
407             }
408 
409             @Override @DefinedBy(Api.LANGUAGE_MODEL)
410             public Void visitRequires(RequiresDirective d, Void p) {
411                 // requires (static|transitive)* module-name
412                 writer.print("requires ");
413                 if (d.isStatic())
414                     writer.print("static ");
415                 if (d.isTransitive())
416                     writer.print("transitive ");
417                 writer.print(d.getDependency().getQualifiedName());
418                 return null;
419             }
420 
421             @Override @DefinedBy(Api.LANGUAGE_MODEL)
422             public Void visitUses(UsesDirective d, Void p) {
423                 // uses service-name
424                 writer.print("uses ");
425                 writer.print(d.getService().getQualifiedName());
426                 return null;
427             }
428 
429             private void printModuleList(List<? extends ModuleElement> modules) {
430                 if (modules != null) {
431                     writer.print(" to ");
432                     printNameableList(modules);
433                 }
434             }
435 
436             private void printNameableList(List<? extends QualifiedNameable> nameables) {
437                 writer.print(nameables.stream().
438                              map(QualifiedNameable::getQualifiedName).
439                              collect(Collectors.joining(", ")));
440             }
441         }
442 
443         public void flush() {
444             writer.flush();
445         }
446 
447         private void printDocComment(Element e) {
448             String docComment = elementUtils.getDocComment(e);
449 
450             if (docComment != null) {
451                 // Break comment into lines
452                 java.util.StringTokenizer st = new StringTokenizer(docComment,
453                                                                   "\n\r");
454                 indent();
455                 writer.println("/**");
456 
457                 while(st.hasMoreTokens()) {
458                     indent();
459                     writer.print(" *");
460                     writer.println(st.nextToken());
461                 }
462 
463                 indent();
464                 writer.println(" */");
465             }
466         }
467 
468         private void printModifiers(Element e) {
469             ElementKind kind = e.getKind();
470             if (kind == PARAMETER || kind == RECORD_COMPONENT) {
471                 // Print annotation inline
472                 writer.print(annotationsToString(e));
473             } else {
474                 printAnnotations(e);
475                 indent();
476             }
477 
478             if (kind == ENUM_CONSTANT || kind == RECORD_COMPONENT)
479                 return;
480 
481             Set<Modifier> modifiers = new LinkedHashSet<>();
482             modifiers.addAll(e.getModifiers());
483 
484             switch (kind) {
485             case ANNOTATION_TYPE:
486             case INTERFACE:
487                 modifiers.remove(Modifier.ABSTRACT);
488                 break;
489 
490             case ENUM:
491                 modifiers.remove(Modifier.FINAL);
492                 modifiers.remove(Modifier.ABSTRACT);
493                 modifiers.remove(Modifier.SEALED);
494                 break;
495 
496             case RECORD:
497                 modifiers.remove(Modifier.FINAL);
498                 break;
499 
500             case METHOD:
501             case FIELD:
502                 Element enclosingElement = e.getEnclosingElement();
503                 if (enclosingElement != null) {
504                     if (enclosingElement.getKind().isInterface()) {
505                         modifiers.remove(Modifier.PUBLIC);
506                         modifiers.remove(Modifier.ABSTRACT); // only for methods
507                         modifiers.remove(Modifier.STATIC);   // only for fields
508                         modifiers.remove(Modifier.FINAL);    // only for fields
509                     } else if (enclosingElement.getKind() == RECORD) {
510                         modifiers.remove(Modifier.STRICTFP);
511                     }
512                 }
513                 break;
514 
515             }
516             if (!modifiers.isEmpty()) {
517                 writer.print(modifiers.stream()
518                              .map(Modifier::toString)
519                              .collect(Collectors.joining(" ", "", " ")));
520             }
521         }
522 
523         private void printFormalTypeParameters(Parameterizable e,
524                                                boolean pad) {
525             List<? extends TypeParameterElement> typeParams = e.getTypeParameters();
526             if (!typeParams.isEmpty()) {
527                 writer.print(typeParams.stream()
528                              .map(tpe -> annotationsToString(tpe) + tpe.toString() + printTypeVariableBoundsIfNeeded(tpe))
529                              .collect(Collectors.joining(", ", "<", ">")));
530                 if (pad)
531                     writer.print(" ");
532             }
533         }
534 
535         private String printTypeVariableBoundsIfNeeded(TypeParameterElement tpe) {
536             List<? extends TypeMirror> printableBounds =
537                     tpe.getBounds()
538                        .stream()
539                        .filter(type -> isImportantType(type))
540                        .toList();
541 
542             if (printableBounds.isEmpty()) {
543                 return "";
544             }
545 
546             return " extends " + printableBounds.stream()
547                                                 .map(t -> t.toString())
548                                                 .collect(Collectors.joining(" & "));
549         }
550 
551         private String annotationsToString(Element e) {
552             List<? extends AnnotationMirror> annotations = e.getAnnotationMirrors();
553             return annotations.isEmpty() ?
554                 "" :
555                 annotations.stream()
556                 .map(AnnotationMirror::toString)
557                 .collect(Collectors.joining(" ", "", " "));
558         }
559 
560         private void printAnnotations(Element e) {
561             List<? extends AnnotationMirror> annots = e.getAnnotationMirrors();
562             for(AnnotationMirror annotationMirror : annots) {
563                 // Handle compiler-generated container annotations specially
564                 if (!printedContainerAnnotation(e, annotationMirror)) {
565                     indent();
566                     writer.println(annotationMirror);
567                 }
568             }
569         }
570 
571         private boolean printedContainerAnnotation(Element e,
572                                                    AnnotationMirror annotationMirror) {
573             /*
574              * If the annotation mirror is marked as mandated and
575              * looks like a container annotation, elide printing the
576              * container and just print the wrapped contents.
577              */
578             if (elementUtils.getOrigin(e, annotationMirror) == Elements.Origin.MANDATED) {
579                 // From JLS Chapter 9, an annotation interface AC is a
580                 // containing annotation interface of A if AC declares
581                 // a value() method whose return type is A[] and any
582                 // methods declared by AC other than value() have a
583                 // default value. As an implementation choice, if more
584                 // than one annotation element is found on the outer
585                 // annotation, in other words, something besides a
586                 // "value" method, the annotation will not be treated
587                 // as a wrapper for the purposes of printing. These
588                 // checks are intended to preserve correctness in the
589                 // face of some other kind of annotation being marked
590                 // as mandated.
591 
592                 var entries = annotationMirror.getElementValues().entrySet();
593                 if (entries.size() == 1) {
594                     var annotationType = annotationMirror.getAnnotationType();
595                     var annotationTypeAsElement = annotationType.asElement();
596 
597                     var entry = entries.iterator().next();
598                     var annotationElements = entry.getValue();
599 
600                     // Check that the annotation type declaration has
601                     // a single method named "value" and that it
602                     // returns an array. A stricter check would be
603                     // that it is an array of an annotation type and
604                     // that annotation type in turn was repeatable.
605                     if (annotationTypeAsElement.getKind() == ElementKind.ANNOTATION_TYPE) {
606                         var annotationMethods =
607                             ElementFilter.methodsIn(annotationTypeAsElement.getEnclosedElements());
608                         if (annotationMethods.size() == 1) {
609                             var valueMethod = annotationMethods.get(0);
610                             var returnType = valueMethod.getReturnType();
611 
612                             if ("value".equals(valueMethod.getSimpleName().toString()) &&
613                                 returnType.getKind() == TypeKind.ARRAY) {
614                                 // Use annotation value visitor that
615                                 // returns a boolean if it prints out
616                                 // contained annotations as expected
617                                 // and false otherwise
618 
619                                 return (new SimpleAnnotationValueVisitor14<Boolean, Void>(false) {
620                                     @Override
621                                     public Boolean visitArray(List<? extends AnnotationValue> vals, Void p) {
622                                         if (vals.size() < 2) {
623                                             return false;
624                                         } else {
625                                             for (var annotValue: vals) {
626                                                 indent();
627                                                 writer.println(annotValue.toString());
628                                             }
629                                             return true;
630                                         }
631                                     }
632                                 }).visit(annotationElements);
633                             }
634                         }
635                     }
636                 }
637             }
638             return false;
639         }
640 
641         // TODO: Refactor
642         private void printParameters(ExecutableElement e) {
643             List<? extends VariableElement> parameters = e.getParameters();
644             int size = parameters.size();
645 
646             switch (size) {
647             case 0:
648                 break;
649 
650             case 1:
651                 for(VariableElement parameter: parameters) {
652                     printModifiers(parameter);
653 
654                     if (e.isVarArgs() ) {
655                         TypeMirror tm = parameter.asType();
656                         if (tm.getKind() != TypeKind.ARRAY)
657                             throw new AssertionError("Var-args parameter is not an array type: " + tm);
658                         writer.print((ArrayType.class.cast(tm)).getComponentType() );
659                         writer.print("...");
660                     } else
661                         writer.print(parameter.asType());
662                     writer.print(" " + parameter.getSimpleName());
663                 }
664                 break;
665 
666             default:
667                 {
668                     int i = 1;
669                     for(VariableElement parameter: parameters) {
670                         if (i == 2)
671                             indentation++;
672 
673                         if (i > 1)
674                             indent();
675 
676                         printModifiers(parameter);
677 
678                         if (i == size && e.isVarArgs() ) {
679                             TypeMirror tm = parameter.asType();
680                             if (tm.getKind() != TypeKind.ARRAY)
681                                 throw new AssertionError("Var-args parameter is not an array type: " + tm);
682                                     writer.print((ArrayType.class.cast(tm)).getComponentType() );
683 
684                             writer.print("...");
685                         } else
686                             writer.print(parameter.asType());
687                         writer.print(" " + parameter.getSimpleName());
688 
689                         if (i < size)
690                             writer.println(",");
691 
692                         i++;
693                     }
694 
695                     if (parameters.size() >= 2)
696                         indentation--;
697                 }
698                 break;
699             }
700         }
701 
702         private void printInterfaces(TypeElement e) {
703             ElementKind kind = e.getKind();
704 
705             if(kind != ANNOTATION_TYPE) {
706                 List<? extends TypeMirror> interfaces = e.getInterfaces();
707                 if (!interfaces.isEmpty()) {
708                     writer.print((kind.isClass() ? " implements " : " extends "));
709                     writer.print(interfaces.stream()
710                                  .map(TypeMirror::toString)
711                                  .collect(Collectors.joining(", ")));
712                 }
713             }
714         }
715 
716         private void printPermittedSubclasses(TypeElement e) {
717             if (e.getKind() == ENUM) {
718                 // any permitted classes on an enum are anonymous
719                 // classes for enum bodies, elide.
720                 return;
721             }
722 
723             List<? extends TypeMirror> subtypes = e.getPermittedSubclasses();
724             if (!subtypes.isEmpty()) { // could remove this check with more complicated joining call
725                 writer.print(" permits ");
726                 writer.print(subtypes
727                              .stream()
728                              .map(subtype -> subtype.toString())
729                              .collect(Collectors.joining(", ")));
730             }
731         }
732 
733         private void printThrows(ExecutableElement e) {
734             List<? extends TypeMirror> thrownTypes = e.getThrownTypes();
735             final int size = thrownTypes.size();
736             if (size != 0) {
737                 writer.print(" throws");
738 
739                 int i = 1;
740                 for(TypeMirror thrownType: thrownTypes) {
741                     if (i == 1)
742                         writer.print(" ");
743 
744                     if (i == 2)
745                         indentation++;
746 
747                     if (i >= 2)
748                         indent();
749 
750                     writer.print(thrownType);
751 
752                     if (i != size)
753                         writer.println(", ");
754 
755                     i++;
756                 }
757 
758                 if (size >= 2)
759                     indentation--;
760             }
761         }
762 
763         private static final String [] spaces = {
764             "",
765             "  ",
766             "    ",
767             "      ",
768             "        ",
769             "          ",
770             "            ",
771             "              ",
772             "                ",
773             "                  ",
774             "                    "
775         };
776 
777         private void indent() {
778             int indentation = this.indentation;
779             if (indentation < 0)
780                 return;
781             final int maxIndex = spaces.length - 1;
782 
783             while (indentation > maxIndex) {
784                 writer.print(spaces[maxIndex]);
785                 indentation -= maxIndex;
786             }
787             writer.print(spaces[indentation]);
788         }
789 
790         /**{@return true if this type is either not {@code java.lang.Object},
791          * or is annotated, and hence needs to be included in the output,
792          * even for cases where there's implicit {@code java.lang.Object} type.}
793          *
794          * @param type the type to check.
795          */
796         private boolean isImportantType(TypeMirror type) {
797             if (!type.getAnnotationMirrors().isEmpty()) {
798                 return true;
799             }
800             TypeElement e2 = (TypeElement)
801                 ((DeclaredType) type).asElement();
802             if (!e2.getKind().isClass()) {
803                 return true;
804             }
805             return e2.getSuperclass().getKind() != TypeKind.NONE;
806         }
807     }
808 }