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 }