xref: /aosp_15_r20/external/doclava/src/com/google/doclava/Doclava.java (revision feeed43c7c55e85932c547a3cefc559df175227c)
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.doclava;
18 
19 import static java.util.stream.Collectors.toList;
20 
21 import com.google.clearsilver.jsilver.JSilver;
22 import com.google.clearsilver.jsilver.data.Data;
23 import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader;
24 import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader;
25 import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader;
26 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
27 import com.google.doclava.Errors.ErrorMessage;
28 import com.google.doclava.Errors.LintBaselineEntry;
29 import com.google.doclava.javadoc.RootDocImpl;
30 import com.sun.javadoc.ClassDoc;
31 import com.sun.javadoc.Doc;
32 import com.sun.javadoc.MemberDoc;
33 import com.sun.javadoc.RootDoc;
34 import com.sun.javadoc.Type;
35 import java.io.BufferedInputStream;
36 import java.io.BufferedOutputStream;
37 import java.io.BufferedReader;
38 import java.io.BufferedWriter;
39 import java.io.ByteArrayOutputStream;
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FileNotFoundException;
43 import java.io.FileOutputStream;
44 import java.io.FileReader;
45 import java.io.FileWriter;
46 import java.io.IOException;
47 import java.io.PrintStream;
48 import java.lang.reflect.Array;
49 import java.lang.reflect.InvocationHandler;
50 import java.lang.reflect.InvocationTargetException;
51 import java.lang.reflect.Method;
52 import java.lang.reflect.Proxy;
53 import java.net.MalformedURLException;
54 import java.net.URL;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.Collection;
58 import java.util.Collections;
59 import java.util.HashMap;
60 import java.util.HashSet;
61 import java.util.List;
62 import java.util.Locale;
63 import java.util.Map;
64 import java.util.Set;
65 import java.util.SortedMap;
66 import java.util.TreeMap;
67 import java.util.function.Function;
68 import java.util.jar.JarFile;
69 import java.util.regex.Matcher;
70 import java.util.regex.Pattern;
71 import java.util.stream.Collectors;
72 import javax.lang.model.SourceVersion;
73 import jdk.javadoc.doclet.Doclet;
74 import jdk.javadoc.doclet.DocletEnvironment;
75 import jdk.javadoc.doclet.Reporter;
76 
77 public class Doclava implements Doclet {
78 
79   private static final String SDK_CONSTANT_ANNOTATION = "android.annotation.SdkConstant";
80   private static final String SDK_CONSTANT_TYPE_ACTIVITY_ACTION =
81       "android.annotation.SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION";
82   private static final String SDK_CONSTANT_TYPE_BROADCAST_ACTION =
83       "android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION";
84   private static final String SDK_CONSTANT_TYPE_SERVICE_ACTION =
85       "android.annotation.SdkConstant.SdkConstantType.SERVICE_ACTION";
86   private static final String SDK_CONSTANT_TYPE_CATEGORY =
87       "android.annotation.SdkConstant.SdkConstantType.INTENT_CATEGORY";
88   private static final String SDK_CONSTANT_TYPE_FEATURE =
89       "android.annotation.SdkConstant.SdkConstantType.FEATURE";
90   private static final String SDK_WIDGET_ANNOTATION = "android.annotation.Widget";
91   private static final String SDK_LAYOUT_ANNOTATION = "android.annotation.Layout";
92 
93   private static final int TYPE_NONE = 0;
94   private static final int TYPE_WIDGET = 1;
95   private static final int TYPE_LAYOUT = 2;
96   private static final int TYPE_LAYOUT_PARAM = 3;
97 
98   public static final int SHOW_PUBLIC = 0x00000001;
99   public static final int SHOW_PROTECTED = 0x00000003;
100   public static final int SHOW_PACKAGE = 0x00000007;
101   public static final int SHOW_PRIVATE = 0x0000000f;
102   public static final int SHOW_HIDDEN = 0x0000001f;
103 
104   public static int showLevel = SHOW_PROTECTED;
105 
106   public static final boolean SORT_BY_NAV_GROUPS = true;
107   /* Debug output for PageMetadata, format urls from site root */
108   public static boolean META_DBG=false;
109   /* Generate the static html docs with devsite tempating only */
110   public static boolean DEVSITE_STATIC_ONLY = false;
111   /* Don't resolve @link refs found in devsite pages */
112   public static boolean DEVSITE_IGNORE_JDLINKS = false;
113   /* Show Preview navigation and process preview docs */
114   public static boolean INCLUDE_PREVIEW = false;
115   /* output en, es, ja without parent intl/ container */
116   public static boolean USE_DEVSITE_LOCALE_OUTPUT_PATHS = false;
117   /* generate navtree.js without other docs */
118   public static boolean NAVTREE_ONLY = false;
119   /* Generate reference navtree.js with all inherited members */
120   public static boolean AT_LINKS_NAVTREE = false;
121   public static boolean METALAVA_API_SINCE = false;
122   public static String outputPathBase = "/";
123   public static ArrayList<String> inputPathHtmlDirs = new ArrayList<String>();
124   public static ArrayList<String> inputPathHtmlDir2 = new ArrayList<String>();
125   public static String inputPathResourcesDir;
126   public static String outputPathResourcesDir;
127   public static String outputPathHtmlDirs;
128   public static String outputPathHtmlDir2;
129   /* Javadoc output directory and included in url path */
130   public static String javadocDir = "reference/";
131   public static String htmlExtension;
132 
133   public static RootDoc root;
134   public static ArrayList<String[]> mHDFData = new ArrayList<String[]>();
135   public static List<PageMetadata.Node> sTaglist = new ArrayList<PageMetadata.Node>();
136   public static ArrayList<SampleCode> sampleCodes = new ArrayList<SampleCode>();
137   public static ArrayList<SampleCode> sampleCodeGroups = new ArrayList<SampleCode>();
138   public static Data samplesNavTree;
139   public static Map<Character, String> escapeChars = new HashMap<Character, String>();
140   public static String title = "";
141   public static SinceTagger sinceTagger = new SinceTagger();
142   public static ArtifactTagger artifactTagger = new ArtifactTagger();
143   public static HashSet<String> knownTags = new HashSet<String>();
144   public static FederationTagger federationTagger = new FederationTagger();
145   public static boolean showUnannotated = false;
146   public static Set<String> showAnnotations = new HashSet<String>();
147   public static Set<String> hideAnnotations = new HashSet<String>();
148   public static boolean showAnnotationOverridesVisibility = false;
149   public static Set<String> hiddenPackages = new HashSet<String>();
150   public static boolean includeAssets = true;
151   public static boolean includeDefaultAssets = true;
152   private static boolean generateDocs = true;
153   private static boolean parseComments = false;
154   private static String yamlNavFile = null;
155   public static boolean documentAnnotations = false;
156   public static String documentAnnotationsPath = null;
157   public static Map<String, String> annotationDocumentationMap = null;
158   public static boolean referenceOnly = false;
159   public static boolean staticOnly = false;
160   public static boolean yamlV2 = false; /* whether to build the new version of the yaml file */
161   public static boolean devsite = false; /* whether to build docs for devsite */
162   public static AuxSource auxSource = new EmptyAuxSource();
163   public static Linter linter = new EmptyLinter();
164   public static boolean android = false;
165   public static String manifestFile = null;
166   public static String compatConfig = null;
167   public static Map<String, String> manifestPermissions = new HashMap<>();
168 
169   public static JSilver jSilver = null;
170 
171   //API reference extensions
172   private static boolean gmsRef = false;
173   private static boolean gcmRef = false;
174   public static String libraryRoot = null;
175   private static boolean samplesRef = false;
176   private static boolean sac = false;
177 
178     private static ArrayList<String> knownTagsFiles = new ArrayList<>();
179     private static String keepListFile;
180     private static String proguardFile;
181     private static String proofreadFile;
182     private static String todoFile;
183     private static String lintBaselineFile;
184     private static String stubsDir;
185     private static HashSet<String> stubPackages;
186     private static HashSet<String> stubImportPackages;
187     private static boolean stubSourceOnly;
188     private static boolean keepStubComments;
189     private static String sdkValuePath;
190     private static String apiFile;
191     private static String dexApiFile;
192     private static String removedApiFile;
193     private static String removedDexApiFile;
194     private static String exactApiFile;
195     private static String privateApiFile;
196     private static String privateDexApiFile;
197     private static String apiMappingFile;
198     private static boolean offlineMode;
199 
200     @Override
init(Locale locale, Reporter reporter)201     public void init(Locale locale, Reporter reporter) {
202         keepListFile = null;
203         proguardFile = null;
204         proofreadFile = null;
205         todoFile = null;
206         sdkValuePath = null;
207         stubsDir = null;
208         // Create the dependency graph for the stubs  directory
209         offlineMode = false;
210         apiFile = null;
211         dexApiFile = null;
212         removedApiFile = null;
213         removedDexApiFile = null;
214         exactApiFile = null;
215         privateApiFile = null;
216         privateDexApiFile = null;
217         apiMappingFile = null;
218         stubPackages = null;
219         stubImportPackages = null;
220         stubSourceOnly = false;
221         keepStubComments = false;
222     }
223 
224     @Override
getName()225     public String getName() {
226         return "Doclava";
227     }
228 
229     /**
230      * @implNote
231      * {@code -overview} option used to be a built-in parameter in javadoc
232      * tool, and with new Doclet APIs it was moved to
233      * {@link jdk.javadoc.doclet.StandardDoclet}, so we have to implement this
234      * functionality by ourselves.
235      */
236     @Override
getSupportedOptions()237     public Set<? extends Option> getSupportedOptions() {
238         Set<Doclet.Option> options = new HashSet<>();
239 
240         options.add(
241                 new Option() {
242                     private final List<String> names = List.of("-overview");
243                     @Override public int          getArgumentCount() { return 1; }
244                     @Override public String       getDescription() {
245                         return "Pick overview documentation from HTML file";
246                     }
247                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
248                     @Override public List<String> getNames() { return names; }
249                     @Override public String       getParameters() { return "<file>"; }
250                     @Override public boolean      process(String opt, List<String> arguments) {
251                         // TODO(nikitai): implement "overview" file inclusion.
252                         //  This used to be built in javadoc tool but in new Doclet APIs it was
253                         //  removed from default functionality and moved to StandardDoclet
254                         //  implementation. In our case we need to implement this on our own.
255                         return true;
256                     }
257                 }
258         );
259 
260         options.add(
261                 new Option() {
262                     private final List<String> names = List.of("-d");
263                     @Override public int          getArgumentCount() { return 1; }
264                     @Override public String       getDescription() {
265                         return "Destination directory for output files";
266                     }
267                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
268                     @Override public List<String> getNames() { return names; }
269                     @Override public String       getParameters() { return "<directory>"; }
270                     @Override public boolean      process(String opt, List<String> arguments) {
271                         outputPathBase = outputPathHtmlDirs = ClearPage.outputDir
272                                 = arguments.get(0);
273                         return true;
274                     }
275                 }
276         );
277 
278         options.add(
279                 new Option() {
280                     private final List<String> names = List.of("-templatedir");
281                     @Override public int          getArgumentCount() { return 1; }
282                     @Override public String       getDescription() {
283                         return "Templates for jSilver template engine used to generate docs";
284                     }
285                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
286                     @Override public List<String> getNames() { return names; }
287                     @Override public String       getParameters() { return "<directory>"; }
288                     @Override public boolean      process(String opt, List<String> arguments) {
289                         ClearPage.addTemplateDir(arguments.get(0));
290                         return true;
291                     }
292                 }
293         );
294 
295         options.add(
296                 new Option() {
297                     private final List<String> names = List.of("-hdf");
298                     @Override public int          getArgumentCount() { return 2; }
299                     @Override public String       getDescription() {
300                         return """
301                                 Doclava uses the jSilver template engine to render docs. This
302                                 option adds a key-value pair to the global data holder object which
303                                 is passed to all render calls. Think of it as a list of default
304                                 parameters for jSilver.""";
305                     }
306                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
307                     @Override public List<String> getNames() { return names; }
308                     @Override public String       getParameters() { return "<key> <value>"; }
309                     @Override public boolean      process(String opt, List<String> arguments) {
310                         mHDFData.add(new String[] { arguments.get(0), arguments.get(1) });
311                         return true;
312                     }
313                 }
314         );
315 
316         options.add(
317                 new Option() {
318                     private final List<String> names = List.of("-knowntags");
319                     @Override public int          getArgumentCount() { return 1; }
320                     @Override public String       getDescription() {
321                         return """
322                                 List of non-standard tags used in sources.
323                                 Example: ${ANDROID_BUILD_TOP}/libcore/known_oj_tags.txt""";
324                     }
325                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
326                     @Override public List<String> getNames() { return names; }
327                     @Override public String       getParameters() { return "<file>"; }
328                     @Override public boolean      process(String opt, List<String> arguments) {
329                         knownTagsFiles.add(arguments.get(0));
330                         return true; }
331                 }
332         );
333 
334         options.add(
335                 new Option() {
336                     private final List<String> names = List.of("-apidocsdir");
337                     @Override public int          getArgumentCount() { return 1; }
338                     @Override public String       getDescription() {
339                         return """
340                                 Javadoc output directory path relative to root, which is specified \
341                                 with '-d root'
342 
343                                 Default value: 'reference/'""";
344                     }
345                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
346                     @Override public List<String> getNames() { return names; }
347                     @Override public String       getParameters() { return "<path>"; }
348                     @Override public boolean      process(String opt, List<String> arguments) {
349                         javadocDir = arguments.get(0);
350                         return true;
351                     }
352                 }
353         );
354 
355         options.add(
356                 new Option() {
357                     private final List<String> names = List.of("-toroot");
358                     @Override public int          getArgumentCount() { return 1; }
359                     @Override public String       getDescription() {
360                         return """
361                                 Relative path to documentation root.
362                                 If set, use <path> as a (relative or absolute) link to \
363                                 documentation root in .html pages.
364 
365                                 If not set, an auto-generated path traversal links will be used, \
366                                 e.g. “../../../”.
367                                 """;
368                     }
369                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
370                     @Override public List<String> getNames() { return names; }
371                     @Override public String       getParameters() { return "<path>"; }
372                     @Override public boolean      process(String opt, List<String> arguments) {
373                         ClearPage.toroot = arguments.get(0);
374                         return true; }
375                 }
376         );
377 
378         options.add(
379                 new Option() {
380                     private final List<String> names = List.of("-samplecode");
381                     @Override public int          getArgumentCount() { return 3; }
382                     @Override public String       getDescription() {
383                         return """
384                                 Adds a browsable sample code project from <source> directory under \
385                                 <dest> path relative to root (specified with '-d' <directory>) and \
386                                 named <title>.
387                                 """;
388                     }
389                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
390                     @Override public List<String> getNames() { return names; }
391                     @Override public String       getParameters() {
392                         return "<source> <dest> <title>";
393                     }
394                     @Override public boolean      process(String opt, List<String> arguments) {
395                         sampleCodes.add(new SampleCode(arguments.get(0), arguments.get(1), arguments.get(2)));
396                         samplesRef = true;
397                         return true;
398                     }
399                 }
400         );
401 
402         options.add(
403                 new Option() {
404                     private final List<String> names = List.of("-samplegroup");
405                     @Override public int          getArgumentCount() { return 1; }
406                     @Override public String       getDescription() {
407                         return "Add a sample code project group";
408                     }
409                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
410                     @Override public List<String> getNames() { return names; }
411                     @Override public String       getParameters() { return "<group>"; }
412                     @Override public boolean      process(String opt, List<String> arguments) {
413                         sampleCodeGroups.add(new SampleCode(null, null, arguments.get(0)));
414                         return true;
415                     }
416                 }
417         );
418 
419         options.add(
420                 new Option() {
421                     private final List<String> names = List.of("-samplesdir");
422                     @Override public int          getArgumentCount() { return 1; }
423                     @Override public String       getDescription() {
424                         return """
425                                 Directory where to look for samples. Android uses \
426                                 ${ANDROID_BUILD_TOP}/development/samples/browseable.
427                                 """;
428                     }
429                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
430                     @Override public List<String> getNames() { return names; }
431                     @Override public String       getParameters() { return "<directory>"; }
432                     @Override public boolean      process(String opt, List<String> arguments) {
433                         samplesRef = true;
434                         getSampleProjects(new File(arguments.get(0)));
435                         return true;
436                     }
437                 }
438         );
439 
440         options.add(
441                 new Option() {
442                     private final List<String> names = List.of("-htmldir");
443                     @Override public int          getArgumentCount() { return 1; }
444                     @Override public String       getDescription() { return ""; }
445                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
446                     @Override public List<String> getNames() { return names; }
447                     @Override public String       getParameters() { return "<path>"; }
448                     @Override public boolean      process(String opt, List<String> arguments) {
449                         inputPathHtmlDirs.add(arguments.get(0));
450                         ClearPage.htmlDirs = inputPathHtmlDirs;
451                         return true;
452                     }
453                 }
454         );
455 
456         options.add(
457                 new Option() {
458                     private final List<String> names = List.of("-htmldir2");
459                     @Override public int          getArgumentCount() { return 2; }
460                     @Override public String       getDescription() { return ""; }
461                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
462                     @Override public List<String> getNames() { return names; }
463                     @Override public String       getParameters() {
464                         return "<input_path> <output_path>";
465                     }
466                     @Override public boolean      process(String opt, List<String> arguments) {
467                         if (arguments.get(1).equals("default")) {
468                             inputPathHtmlDir2.add(arguments.get(0));
469                         } else {
470                             inputPathHtmlDir2.add(arguments.get(0));
471                             outputPathHtmlDir2 = arguments.get(1);
472                         }
473                         return true;
474                     }
475                 }
476         );
477 
478         options.add(
479                 new Option() {
480                     private final List<String> names = List.of("-resourcesdir");
481                     @Override public int          getArgumentCount() { return 1; }
482                     @Override public String       getDescription() { return ""; }
483                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
484                     @Override public List<String> getNames() { return names; }
485                     @Override public String       getParameters() { return "<path>"; }
486                     @Override public boolean      process(String opt, List<String> arguments) {
487                         inputPathResourcesDir = arguments.get(0);
488                         return true;
489                     }
490                 }
491         );
492 
493         options.add(
494                 new Option() {
495                     private final List<String> names = List.of("-resourcesoutdir");
496                     @Override public int          getArgumentCount() { return 1; }
497                     @Override public String       getDescription() { return ""; }
498                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
499                     @Override public List<String> getNames() { return names; }
500                     @Override public String       getParameters() { return "<path>"; }
501                     @Override public boolean      process(String opt, List<String> arguments) {
502                         outputPathResourcesDir = arguments.get(0);
503                         return true;
504                     }
505                 }
506         );
507 
508         options.add(
509                 new Option() {
510                     private final List<String> names = List.of("-title");
511                     @Override public int          getArgumentCount() { return 1; }
512                     @Override public String       getDescription() { return ""; }
513                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
514                     @Override public List<String> getNames() { return names; }
515                     @Override public String       getParameters() { return "<title>"; }
516                     @Override public boolean      process(String opt, List<String> arguments) {
517                         Doclava.title = arguments.get(0);
518                         return true;
519                     }
520                 }
521         );
522 
523         options.add(
524                 new Option() {
525                     private final List<String> names = List.of("-werror");
526                     @Override public int          getArgumentCount() { return 0; }
527                     @Override public String       getDescription() { return ""; }
528                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
529                     @Override public List<String> getNames() { return names; }
530                     @Override public String       getParameters() { return ""; }
531                     @Override public boolean      process(String opt, List<String> arguments) {
532                         Errors.setWarningsAreErrors(true);
533                         return true;
534                     }
535                 }
536         );
537 
538         options.add(
539                 new Option() {
540                     private final List<String> names = List.of("-lerror");
541                     @Override public int          getArgumentCount() { return 0; }
542                     @Override public String       getDescription() { return ""; }
543                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
544                     @Override public List<String> getNames() { return names; }
545                     @Override public String       getParameters() { return ""; }
546                     @Override public boolean      process(String opt, List<String> arguments) {
547                         Errors.setLintsAreErrors(true);
548                         return true;
549                     }
550                 }
551         );
552 
553         options.add(
554                 new Option() {
555                     private final List<String> names = List.of("-lintbaseline");
556                     @Override public int          getArgumentCount() { return 1; }
557                     @Override public String       getDescription() { return "Allowed lint errors"; }
558                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
559                     @Override public List<String> getNames() { return names; }
560                     @Override public String       getParameters() { return "<file>"; }
561                     @Override public boolean      process(String opt, List<String> arguments) {
562                         lintBaselineFile = arguments.get(0);
563                         return true;
564                     }
565                 }
566         );
567 
568         options.add(
569                 new Option() {
570                     private final List<String> names = List.of("-error");
571                     @Override public int          getArgumentCount() { return 1; }
572                     @Override public String       getDescription() { return ""; }
573                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
574                     @Override public List<String> getNames() { return names; }
575                     @Override public String       getParameters() { return "<code_value>"; }
576                     @Override public boolean      process(String opt, List<String> arguments) {
577                         try {
578                             int level = Integer.parseInt(arguments.get(0));
579                             Errors.setErrorLevel(level, Errors.ERROR);
580                             return true;
581                         } catch (NumberFormatException e) {
582                             return false;
583                         }
584                     }
585                 }
586         );
587 
588         options.add(
589                 new Option() {
590                     private final List<String> names = List.of("-warning");
591                     @Override public int          getArgumentCount() { return 1; }
592                     @Override public String       getDescription() { return ""; }
593                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
594                     @Override public List<String> getNames() { return names; }
595                     @Override public String       getParameters() { return "<code_value>"; }
596                     @Override public boolean      process(String opt, List<String> arguments) {
597                         try {
598                             int level = Integer.parseInt(arguments.get(0));
599                             Errors.setErrorLevel(level, Errors.WARNING);
600                             return true;
601                         } catch (NumberFormatException e) {
602                             return false;
603                         }
604                     }
605                 }
606         );
607 
608         options.add(
609                 new Option() {
610                     private final List<String> names = List.of("-lint");
611                     @Override public int          getArgumentCount() { return 1; }
612                     @Override public String       getDescription() { return ""; }
613                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
614                     @Override public List<String> getNames() { return names; }
615                     @Override public String       getParameters() { return "<code_value>"; }
616                     @Override public boolean      process(String opt, List<String> arguments) {
617                         try {
618                             int level = Integer.parseInt(arguments.get(0));
619                             Errors.setErrorLevel(level, Errors.LINT);
620                             return true;
621                         } catch (NumberFormatException e) {
622                             return false;
623                         }
624                     }
625                 }
626         );
627 
628         options.add(
629                 new Option() {
630                     private final List<String> names = List.of("-hide");
631                     @Override public int          getArgumentCount() { return 1; }
632                     @Override public String       getDescription() { return ""; }
633                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
634                     @Override public List<String> getNames() { return names; }
635                     @Override public String       getParameters() { return "<code_value>"; }
636                     @Override public boolean      process(String opt, List<String> arguments) {
637                         try {
638                             int level = Integer.parseInt(arguments.get(0));
639                             Errors.setErrorLevel(level, Errors.HIDDEN);
640                             return true;
641                         } catch (NumberFormatException e) {
642                             return false;
643                         }
644                     }
645                 }
646         );
647 
648         options.add(
649                 new Option() {
650                     private final List<String> names = List.of("-keeplist");
651                     @Override public int          getArgumentCount() { return 1; }
652                     @Override public String       getDescription() { return ""; }
653                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
654                     @Override public List<String> getNames() { return names; }
655                     @Override public String       getParameters() { return "<list>"; }
656                     @Override public boolean      process(String opt, List<String> arguments) {
657                         keepListFile = arguments.get(0);
658                         return true;
659                     }
660                 }
661         );
662 
663         options.add(
664                 new Option() {
665                     private final List<String> names = List.of("-showUnannotated");
666                     @Override public int          getArgumentCount() { return 0; }
667                     @Override public String       getDescription() { return ""; }
668                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
669                     @Override public List<String> getNames() { return names; }
670                     @Override public String       getParameters() { return ""; }
671                     @Override public boolean      process(String opt, List<String> arguments) {
672                         showUnannotated = true;
673                         return true;
674                     }
675                 }
676         );
677 
678         options.add(
679                 new Option() {
680                     private final List<String> names = List.of("-showAnnotation");
681                     @Override public int          getArgumentCount() { return 1; }
682                     @Override public String       getDescription() { return ""; }
683                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
684                     @Override public List<String> getNames() { return names; }
685                     @Override public String       getParameters() { return "<annotation>"; }
686                     @Override public boolean      process(String opt, List<String> arguments) {
687                         showAnnotations.add(arguments.get(0));
688                         return true;
689                     }
690                 }
691         );
692 
693         options.add(
694                 new Option() {
695                     private final List<String> names = List.of("-hideAnnotation");
696                     @Override public int          getArgumentCount() { return 1; }
697                     @Override public String       getDescription() { return ""; }
698                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
699                     @Override public List<String> getNames() { return names; }
700                     @Override public String       getParameters() { return "<annotation>"; }
701                     @Override public boolean      process(String opt, List<String> arguments) {
702                         hideAnnotations.add(arguments.get(0));
703                         return true;
704                     }
705                 }
706         );
707 
708         options.add(
709                 new Option() {
710                     private final List<String> names = List.of("-showAnnotationOverridesVisibility");
711                     @Override public int          getArgumentCount() { return 0; }
712                     @Override public String       getDescription() { return ""; }
713                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
714                     @Override public List<String> getNames() { return names; }
715                     @Override public String       getParameters() { return ""; }
716                     @Override public boolean      process(String opt, List<String> arguments) {
717                         showAnnotationOverridesVisibility = true;
718                         return true;
719                     }
720                 }
721         );
722 
723         options.add(
724                 new Option() {
725                     private final List<String> names = List.of("-hidePackage");
726                     @Override public int          getArgumentCount() { return 1; }
727                     @Override public String       getDescription() { return ""; }
728                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
729                     @Override public List<String> getNames() { return names; }
730                     @Override public String       getParameters() { return "<package>"; }
731                     @Override public boolean      process(String opt, List<String> arguments) {
732                         hiddenPackages.add(arguments.get(0));
733                         return true;
734                     }
735                 }
736         );
737 
738         options.add(
739                 new Option() {
740                     private final List<String> names = List.of("-proguard");
741                     @Override public int          getArgumentCount() { return 1; }
742                     @Override public String       getDescription() { return ""; }
743                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
744                     @Override public List<String> getNames() { return names; }
745                     @Override public String       getParameters() { return "<arg>"; }
746                     @Override public boolean      process(String opt, List<String> arguments) {
747                         proguardFile = arguments.get(0);
748                         return true;
749                     }
750                 }
751         );
752 
753         options.add(
754                 new Option() {
755                     private final List<String> names = List.of("-proofread");
756                     @Override public int          getArgumentCount() { return 1; }
757                     @Override public String       getDescription() { return ""; }
758                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
759                     @Override public List<String> getNames() { return names; }
760                     @Override public String       getParameters() { return "<arg>"; }
761                     @Override public boolean      process(String opt, List<String> arguments) {
762                         proofreadFile = arguments.get(0);
763                         return true;
764                     }
765                 }
766         );
767 
768         options.add(
769                 new Option() {
770                     private final List<String> names = List.of("-todo");
771                     @Override public int          getArgumentCount() { return 1; }
772                     @Override public String       getDescription() { return ""; }
773                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
774                     @Override public List<String> getNames() { return names; }
775                     @Override public String       getParameters() { return "<file>"; }
776                     @Override public boolean      process(String opt, List<String> arguments) {
777                         todoFile = arguments.get(0);
778                         return true;
779                     }
780                 }
781         );
782 
783         options.add(
784                 new Option() {
785                     private final List<String> names = List.of("-public");
786                     @Override public int          getArgumentCount() { return 0; }
787                     @Override public String       getDescription() { return ""; }
788                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
789                     @Override public List<String> getNames() { return names; }
790                     @Override public String       getParameters() { return ""; }
791                     @Override public boolean      process(String opt, List<String> arguments) {
792                         showLevel = SHOW_PUBLIC;
793                         return true;
794                     }
795                 }
796         );
797 
798         options.add(
799                 new Option() {
800                     private final List<String> names = List.of("-protected");
801                     @Override public int          getArgumentCount() { return 0; }
802                     @Override public String       getDescription() { return ""; }
803                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
804                     @Override public List<String> getNames() { return names; }
805                     @Override public String       getParameters() { return ""; }
806                     @Override public boolean      process(String opt, List<String> arguments) {
807                         showLevel = SHOW_PROTECTED;
808                         return true;
809                     }
810                 }
811         );
812 
813         options.add(
814                 new Option() {
815                     private final List<String> names = List.of("-package");
816                     @Override public int          getArgumentCount() { return 0; }
817                     @Override public String       getDescription() { return ""; }
818                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
819                     @Override public List<String> getNames() { return names; }
820                     @Override public String       getParameters() { return ""; }
821                     @Override public boolean      process(String opt, List<String> arguments) {
822                         showLevel = SHOW_PACKAGE;
823                         return true;
824                     }
825                 }
826         );
827 
828         options.add(
829                 new Option() {
830                     private final List<String> names = List.of("-private");
831                     @Override public int          getArgumentCount() { return 0; }
832                     @Override public String       getDescription() { return ""; }
833                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
834                     @Override public List<String> getNames() { return names; }
835                     @Override public String       getParameters() { return ""; }
836                     @Override public boolean      process(String opt, List<String> arguments) {
837                         showLevel = SHOW_PRIVATE;
838                         return true;
839                     }
840                 }
841         );
842 
843         options.add(
844                 new Option() {
845                     private final List<String> names = List.of("-hidden");
846                     @Override public int          getArgumentCount() { return 0; }
847                     @Override public String       getDescription() { return ""; }
848                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
849                     @Override public List<String> getNames() { return names; }
850                     @Override public String       getParameters() { return ""; }
851                     @Override public boolean      process(String opt, List<String> arguments) {
852                         showLevel = SHOW_HIDDEN;
853                         return true;
854                     }
855                 }
856         );
857 
858         options.add(
859                 new Option() {
860                     private final List<String> names = List.of("-stubs");
861                     @Override public int          getArgumentCount() { return 1; }
862                     @Override public String       getDescription() { return ""; }
863                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
864                     @Override public List<String> getNames() { return names; }
865                     @Override public String       getParameters() { return "<stubs>"; }
866                     @Override public boolean      process(String opt, List<String> arguments) {
867                         stubsDir = arguments.get(0);
868                         return true;
869                     }
870                 }
871         );
872 
873         options.add(
874                 new Option() {
875                     private final List<String> names = List.of("-stubpackages");
876                     @Override public int          getArgumentCount() { return 1; }
877                     @Override public String       getDescription() { return ""; }
878                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
879                     @Override public List<String> getNames() { return names; }
880                     @Override public String       getParameters() { return "<packages>"; }
881                     @Override public boolean      process(String opt, List<String> arguments) {
882                         stubPackages = new HashSet<>();
883                         stubPackages.addAll(Arrays.asList(arguments.get(0).split(":")));
884                         return true;
885                     }
886                 }
887         );
888 
889         options.add(
890                 new Option() {
891                     private final List<String> names = List.of("-stubimportpackages");
892                     @Override public int          getArgumentCount() { return 1; }
893                     @Override public String       getDescription() { return ""; }
894                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
895                     @Override public List<String> getNames() { return names; }
896                     @Override public String       getParameters() { return "<packages>"; }
897                     @Override public boolean      process(String opt, List<String> arguments) {
898                         stubImportPackages = new HashSet<>();
899                         for (String pkg : arguments.get(0).split(":")) {
900                             stubImportPackages.add(pkg);
901                             hiddenPackages.add(pkg);
902                         }
903                         return true;
904                     }
905                 }
906         );
907 
908         options.add(
909                 new Option() {
910                     private final List<String> names = List.of("-stubsourceonly");
911                     @Override public int          getArgumentCount() { return 0; }
912                     @Override public String       getDescription() { return ""; }
913                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
914                     @Override public List<String> getNames() { return names; }
915                     @Override public String       getParameters() { return ""; }
916                     @Override public boolean      process(String opt, List<String> arguments) {
917                         stubSourceOnly = true;
918                         return true;
919                     }
920                 }
921         );
922 
923         options.add(
924                 new Option() {
925                     private final List<String> names = List.of("-keepstubcomments");
926                     @Override public int          getArgumentCount() { return 0; }
927                     @Override public String       getDescription() { return ""; }
928                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
929                     @Override public List<String> getNames() { return names; }
930                     @Override public String       getParameters() { return ""; }
931                     @Override public boolean      process(String opt, List<String> arguments) {
932                         keepStubComments = true;
933                         return true;
934                     }
935                 }
936         );
937 
938         options.add(
939                 new Option() {
940                     private final List<String> names = List.of("-sdkvalues");
941                     @Override public int          getArgumentCount() { return 1; }
942                     @Override public String       getDescription() { return ""; }
943                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
944                     @Override public List<String> getNames() { return names; }
945                     @Override public String       getParameters() { return "<path>"; }
946                     @Override public boolean      process(String opt, List<String> arguments) {
947                         sdkValuePath = arguments.get(0);
948                         return true;
949                     }
950                 }
951         );
952 
953         options.add(
954                 new Option() {
955                     private final List<String> names = List.of("-api");
956                     @Override public int          getArgumentCount() { return 1; }
957                     @Override public String       getDescription() { return ""; }
958                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
959                     @Override public List<String> getNames() { return names; }
960                     @Override public String       getParameters() { return "<file>"; }
961                     @Override public boolean      process(String opt, List<String> arguments) {
962                         apiFile = arguments.get(0);
963                         return true;
964                     }
965                 }
966         );
967 
968         options.add(
969                 new Option() {
970                     private final List<String> names = List.of("-dexApi");
971                     @Override public int          getArgumentCount() { return 1; }
972                     @Override public String       getDescription() { return ""; }
973                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
974                     @Override public List<String> getNames() { return names; }
975                     @Override public String       getParameters() { return "<file>"; }
976                     @Override public boolean      process(String opt, List<String> arguments) {
977                         dexApiFile = arguments.get(0);
978                         return true;
979                     }
980                 }
981         );
982 
983         options.add(
984                 new Option() {
985                     private final List<String> names = List.of("-removedApi");
986                     @Override public int          getArgumentCount() { return 1; }
987                     @Override public String       getDescription() { return ""; }
988                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
989                     @Override public List<String> getNames() { return names; }
990                     @Override public String       getParameters() { return "<file>"; }
991                     @Override public boolean      process(String opt, List<String> arguments) {
992                         removedApiFile = arguments.get(0);
993                         return true;
994                     }
995                 }
996         );
997 
998         options.add(
999                 new Option() {
1000                     private final List<String> names = List.of("-removedDexApi");
1001                     @Override public int          getArgumentCount() { return 1; }
1002                     @Override public String       getDescription() { return ""; }
1003                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1004                     @Override public List<String> getNames() { return names; }
1005                     @Override public String       getParameters() { return "<file>"; }
1006                     @Override public boolean      process(String opt, List<String> arguments) {
1007                         removedDexApiFile = arguments.get(0);
1008                         return true;
1009                     }
1010                 }
1011         );
1012 
1013         options.add(
1014                 new Option() {
1015                     private final List<String> names = List.of("-exactApi");
1016                     @Override public int          getArgumentCount() { return 1; }
1017                     @Override public String       getDescription() { return ""; }
1018                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1019                     @Override public List<String> getNames() { return names; }
1020                     @Override public String       getParameters() { return "<file>"; }
1021                     @Override public boolean      process(String opt, List<String> arguments) {
1022                         exactApiFile = arguments.get(0);
1023                         return true;
1024                     }
1025                 }
1026         );
1027 
1028         options.add(
1029                 new Option() {
1030                     private final List<String> names = List.of("-privateApi");
1031                     @Override public int          getArgumentCount() { return 1; }
1032                     @Override public String       getDescription() { return ""; }
1033                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1034                     @Override public List<String> getNames() { return names; }
1035                     @Override public String       getParameters() { return "<file>"; }
1036                     @Override public boolean      process(String opt, List<String> arguments) {
1037                         privateApiFile = arguments.get(0);
1038                         return true;
1039                     }
1040                 }
1041         );
1042 
1043         options.add(
1044                 new Option() {
1045                     private final List<String> names = List.of("-privateDexApi");
1046                     @Override public int          getArgumentCount() { return 1; }
1047                     @Override public String       getDescription() { return ""; }
1048                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1049                     @Override public List<String> getNames() { return names; }
1050                     @Override public String       getParameters() { return "<file>"; }
1051                     @Override public boolean      process(String opt, List<String> arguments) {
1052                         privateDexApiFile = arguments.get(0);
1053                         return true;
1054                     }
1055                 }
1056         );
1057 
1058         options.add(
1059                 new Option() {
1060                     private final List<String> names = List.of("-apiMapping");
1061                     @Override public int          getArgumentCount() { return 1; }
1062                     @Override public String       getDescription() { return ""; }
1063                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1064                     @Override public List<String> getNames() { return names; }
1065                     @Override public String       getParameters() { return "<file>"; }
1066                     @Override public boolean      process(String opt, List<String> arguments) {
1067                         apiMappingFile = arguments.get(0);
1068                         return true;
1069                     }
1070                 }
1071         );
1072 
1073         options.add(
1074                 new Option() {
1075                     private final List<String> names = List.of("-nodocs");
1076                     @Override public int          getArgumentCount() { return 0; }
1077                     @Override public String       getDescription() { return ""; }
1078                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1079                     @Override public List<String> getNames() { return names; }
1080                     @Override public String       getParameters() { return ""; }
1081                     @Override public boolean      process(String opt, List<String> arguments) {
1082                         generateDocs = false;
1083                         return true;
1084                     }
1085                 }
1086         );
1087 
1088         options.add(
1089                 new Option() {
1090                     private final List<String> names = List.of("-noassets");
1091                     @Override public int          getArgumentCount() { return 0; }
1092                     @Override public String       getDescription() { return ""; }
1093                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1094                     @Override public List<String> getNames() { return names; }
1095                     @Override public String       getParameters() { return ""; }
1096                     @Override public boolean      process(String opt, List<String> arguments) {
1097                         includeAssets = false;
1098                         return true;
1099                     }
1100                 }
1101         );
1102 
1103         options.add(
1104                 new Option() {
1105                     private final List<String> names = List.of("-nodefaultassets");
1106                     @Override public int          getArgumentCount() { return 0; }
1107                     @Override public String       getDescription() { return ""; }
1108                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1109                     @Override public List<String> getNames() { return names; }
1110                     @Override public String       getParameters() { return ""; }
1111                     @Override public boolean      process(String opt, List<String> arguments) {
1112                         includeDefaultAssets = false;
1113                         return true;
1114                     }
1115                 }
1116         );
1117 
1118         options.add(
1119                 new Option() {
1120                     private final List<String> names = List.of("-parsecomments");
1121                     @Override public int          getArgumentCount() { return 0; }
1122                     @Override public String       getDescription() { return ""; }
1123                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1124                     @Override public List<String> getNames() { return names; }
1125                     @Override public String       getParameters() { return ""; }
1126                     @Override public boolean      process(String opt, List<String> arguments) {
1127                         parseComments = true;
1128                         return true;
1129                     }
1130                 }
1131         );
1132 
1133         options.add(
1134                 new Option() {
1135                     private final List<String> names = List.of("-metalavaApiSince");
1136                     @Override public int          getArgumentCount() { return 0; }
1137                     @Override public String       getDescription() { return ""; }
1138                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1139                     @Override public List<String> getNames() { return names; }
1140                     @Override public String       getParameters() { return ""; }
1141                     @Override public boolean      process(String opt, List<String> arguments) {
1142                         METALAVA_API_SINCE = true;
1143                         return true;
1144                     }
1145                 }
1146         );
1147 
1148         options.add(
1149                 new Option() {
1150                     private final List<String> names = List.of("-since");
1151                     @Override public int          getArgumentCount() { return 2; }
1152                     @Override public String       getDescription() { return ""; }
1153                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1154                     @Override public List<String> getNames() { return names; }
1155                     @Override public String       getParameters() { return "<major> <minor>"; }
1156                     @Override public boolean      process(String opt, List<String> arguments) {
1157                         sinceTagger.addVersion(arguments.get(0), arguments.get(1));
1158                         return true;
1159                     }
1160                 }
1161         );
1162 
1163         options.add(
1164                 new Option() {
1165                     private final List<String> names = List.of("-artifact");
1166                     @Override public int          getArgumentCount() { return 2; }
1167                     @Override public String       getDescription() { return ""; }
1168                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1169                     @Override public List<String> getNames() { return names; }
1170                     @Override public String       getParameters() { return "<arg1> <arg2>"; }
1171                     @Override public boolean      process(String opt, List<String> arguments) {
1172                         artifactTagger.addArtifact(arguments.get(0), arguments.get(1));
1173                         return true;
1174                     }
1175                 }
1176         );
1177 
1178         options.add(
1179                 new Option() {
1180                     private final List<String> names = List.of("-offlinemode");
1181                     @Override public int          getArgumentCount() { return 0; }
1182                     @Override public String       getDescription() { return ""; }
1183                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1184                     @Override public List<String> getNames() { return names; }
1185                     @Override public String       getParameters() { return ""; }
1186                     @Override public boolean      process(String opt, List<String> arguments) {
1187                         // TODO(nikitai): This option is not used anywhere, consider removing.
1188                         offlineMode = true;
1189                         return true;
1190                     }
1191                 }
1192         );
1193 
1194         options.add(
1195                 new Option() {
1196                     private final List<String> names = List.of("-metadataDebug");
1197                     @Override public int          getArgumentCount() { return 0; }
1198                     @Override public String       getDescription() { return ""; }
1199                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1200                     @Override public List<String> getNames() { return names; }
1201                     @Override public String       getParameters() { return ""; }
1202                     @Override public boolean      process(String opt, List<String> arguments) {
1203                         META_DBG = true;
1204                         return true;
1205                     }
1206                 }
1207         );
1208 
1209         options.add(
1210                 new Option() {
1211                     private final List<String> names = List.of("-includePreview");
1212                     @Override public int          getArgumentCount() { return 0; }
1213                     @Override public String       getDescription() { return ""; }
1214                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1215                     @Override public List<String> getNames() { return names; }
1216                     @Override public String       getParameters() { return ""; }
1217                     @Override public boolean      process(String opt, List<String> arguments) {
1218                         INCLUDE_PREVIEW = true;
1219                         return true;
1220                     }
1221                 }
1222         );
1223 
1224         options.add(
1225                 new Option() {
1226                     private final List<String> names = List.of("-ignoreJdLinks");
1227                     @Override public int          getArgumentCount() { return 0; }
1228                     @Override public String       getDescription() { return ""; }
1229                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1230                     @Override public List<String> getNames() { return names; }
1231                     @Override public String       getParameters() { return ""; }
1232                     @Override public boolean      process(String opt, List<String> arguments) {
1233                         if (DEVSITE_STATIC_ONLY) {
1234                             DEVSITE_IGNORE_JDLINKS = true;
1235                         }
1236                         return true;
1237                     }
1238                 }
1239         );
1240 
1241         options.add(
1242                 new Option() {
1243                     private final List<String> names = List.of("-federate");
1244                     @Override public int          getArgumentCount() { return 2; }
1245                     @Override public String       getDescription() { return ""; }
1246                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1247                     @Override public List<String> getNames() { return names; }
1248                     @Override public String       getParameters() { return "<name> <URL>"; }
1249                     @Override public boolean      process(String opt, List<String> arguments) {
1250                         try {
1251                             String name = arguments.get(0);
1252                             URL federationURL = new URL(arguments.get(1));
1253                             federationTagger.addSiteUrl(name, federationURL);
1254                         } catch (MalformedURLException e) {
1255                             System.err.println("Could not parse URL for federation: " + arguments.get(0));
1256                             return false;
1257                         }
1258                         return true;
1259                     }
1260                 }
1261         );
1262 
1263         options.add(
1264                 new Option() {
1265                     private final List<String> names = List.of("-federationapi");
1266                     @Override public int          getArgumentCount() { return 2; }
1267                     @Override public String       getDescription() { return ""; }
1268                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1269                     @Override public List<String> getNames() { return names; }
1270                     @Override public String       getParameters() { return "<name> <file>"; }
1271                     @Override public boolean      process(String opt, List<String> arguments) {
1272                         String name = arguments.get(0);
1273                         String file = arguments.get(1);
1274                         federationTagger.addSiteApi(name, file);
1275                         return true;
1276                     }
1277                 }
1278         );
1279 
1280         options.add(
1281                 new Option() {
1282                     private final List<String> names = List.of("-gmsref");
1283                     @Override public int          getArgumentCount() { return 0; }
1284                     @Override public String       getDescription() { return ""; }
1285                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1286                     @Override public List<String> getNames() { return names; }
1287                     @Override public String       getParameters() { return ""; }
1288                     @Override public boolean      process(String opt, List<String> arguments) {
1289                         gmsRef = true;
1290                         return true;
1291                     }
1292                 }
1293         );
1294 
1295         options.add(
1296                 new Option() {
1297                     private final List<String> names = List.of("-gcmref");
1298                     @Override public int          getArgumentCount() { return 0; }
1299                     @Override public String       getDescription() { return ""; }
1300                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1301                     @Override public List<String> getNames() { return names; }
1302                     @Override public String       getParameters() { return ""; }
1303                     @Override public boolean      process(String opt, List<String> arguments) {
1304                         gcmRef = true;
1305                         return true;
1306                     }
1307                 }
1308         );
1309 
1310         options.add(
1311                 new Option() {
1312                     private final List<String> names = List.of("-yaml");
1313                     @Override public int          getArgumentCount() { return 1; }
1314                     @Override public String       getDescription() { return ""; }
1315                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1316                     @Override public List<String> getNames() { return names; }
1317                     @Override public String       getParameters() { return "<file>"; }
1318                     @Override public boolean      process(String opt, List<String> arguments) {
1319                         yamlNavFile = arguments.get(0);
1320                         return true;
1321                     }
1322                 }
1323         );
1324 
1325         options.add(
1326                 new Option() {
1327                     private final List<String> names = List.of("-dac_libraryroot");
1328                     @Override public int          getArgumentCount() { return 1; }
1329                     @Override public String       getDescription() { return ""; }
1330                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1331                     @Override public List<String> getNames() { return names; }
1332                     @Override public String       getParameters() { return "<library_root>"; }
1333                     @Override public boolean      process(String opt, List<String> arguments) {
1334                         libraryRoot = ensureSlash(arguments.get(0));
1335                         mHDFData.add(new String[] {"library.root", arguments.get(0)});
1336                         return true;
1337                     }
1338                 }
1339         );
1340 
1341         options.add(
1342                 new Option() {
1343                     private final List<String> names = List.of("-dac_dataname");
1344                     @Override public int          getArgumentCount() { return 1; }
1345                     @Override public String       getDescription() { return ""; }
1346                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1347                     @Override public List<String> getNames() { return names; }
1348                     @Override public String       getParameters() { return "<data_name>"; }
1349                     @Override public boolean      process(String opt, List<String> arguments) {
1350                         mHDFData.add(new String[] {"dac_dataname", arguments.get(0)});
1351                         return true;
1352                     }
1353                 }
1354         );
1355 
1356         options.add(
1357                 new Option() {
1358                     private final List<String> names = List.of("-documentannotations");
1359                     @Override public int          getArgumentCount() { return 1; }
1360                     @Override public String       getDescription() { return ""; }
1361                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1362                     @Override public List<String> getNames() { return names; }
1363                     @Override public String       getParameters() { return "<path>"; }
1364                     @Override public boolean      process(String opt, List<String> arguments) {
1365                         documentAnnotations = true;
1366                         documentAnnotationsPath = arguments.get(0);
1367                         return true;
1368                     }
1369                 }
1370         );
1371 
1372         options.add(
1373                 new Option() {
1374                     private final List<String> names = List.of("-referenceonly");
1375                     @Override public int          getArgumentCount() { return 0; }
1376                     @Override public String       getDescription() { return ""; }
1377                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1378                     @Override public List<String> getNames() { return names; }
1379                     @Override public String       getParameters() { return ""; }
1380                     @Override public boolean      process(String opt, List<String> arguments) {
1381                         referenceOnly = true;
1382                         mHDFData.add(new String[] {"referenceonly", "1"});
1383                         return true;
1384                     }
1385                 }
1386         );
1387 
1388         options.add(
1389                 new Option() {
1390                     private final List<String> names = List.of("-staticonly");
1391                     @Override public int          getArgumentCount() { return 0; }
1392                     @Override public String       getDescription() { return ""; }
1393                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1394                     @Override public List<String> getNames() { return names; }
1395                     @Override public String       getParameters() { return ""; }
1396                     @Override public boolean      process(String opt, List<String> arguments) {
1397                         staticOnly = true;
1398                         mHDFData.add(new String[] {"staticonly", "1"});
1399                         return true;
1400                     }
1401                 }
1402         );
1403 
1404         options.add(
1405                 new Option() {
1406                     private final List<String> names = List.of("-navtreeonly");
1407                     @Override public int          getArgumentCount() { return 0; }
1408                     @Override public String       getDescription() { return ""; }
1409                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1410                     @Override public List<String> getNames() { return names; }
1411                     @Override public String       getParameters() { return ""; }
1412                     @Override public boolean      process(String opt, List<String> arguments) {
1413                         NAVTREE_ONLY = true;
1414                         return true;
1415                     }
1416                 }
1417         );
1418 
1419         options.add(
1420                 new Option() {
1421                     private final List<String> names = List.of("-atLinksNavtree");
1422                     @Override public int          getArgumentCount() { return 0; }
1423                     @Override public String       getDescription() { return ""; }
1424                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1425                     @Override public List<String> getNames() { return names; }
1426                     @Override public String       getParameters() { return ""; }
1427                     @Override public boolean      process(String opt, List<String> arguments) {
1428                         AT_LINKS_NAVTREE = true;
1429                         return true;
1430                     }
1431                 }
1432         );
1433 
1434         options.add(
1435                 new Option() {
1436                     private final List<String> names = List.of("-yamlV2");
1437                     @Override public int          getArgumentCount() { return 0; }
1438                     @Override public String       getDescription() { return ""; }
1439                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1440                     @Override public List<String> getNames() { return names; }
1441                     @Override public String       getParameters() { return ""; }
1442                     @Override public boolean      process(String opt, List<String> arguments) {
1443                         yamlV2 = true;
1444                         return true;
1445                     }
1446                 }
1447         );
1448 
1449         options.add(
1450                 new Option() {
1451                     private final List<String> names = List.of("-devsite");
1452                     @Override public int          getArgumentCount() { return 0; }
1453                     @Override public String       getDescription() { return ""; }
1454                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1455                     @Override public List<String> getNames() { return names; }
1456                     @Override public String       getParameters() { return ""; }
1457                     @Override public boolean      process(String opt, List<String> arguments) {
1458                         devsite = true;
1459                         // Don't copy any assets to devsite output
1460                         includeAssets = false;
1461                         USE_DEVSITE_LOCALE_OUTPUT_PATHS = true;
1462                         mHDFData.add(new String[] {"devsite", "1"});
1463                         if (staticOnly) {
1464                             DEVSITE_STATIC_ONLY = true;
1465                             System.out.println("  ... Generating static html only for devsite");
1466                         }
1467                         if (yamlNavFile == null) {
1468                             // Use _toc.yaml as default to avoid clobbering possible manual _book.yaml files
1469                             yamlNavFile = "_toc.yaml";
1470                         }
1471                         return true;
1472                     }
1473                 }
1474         );
1475 
1476         options.add(
1477                 new Option() {
1478                     private final List<String> names = List.of("-android");
1479                     @Override public int          getArgumentCount() { return 0; }
1480                     @Override public String       getDescription() { return ""; }
1481                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1482                     @Override public List<String> getNames() { return names; }
1483                     @Override public String       getParameters() { return ""; }
1484                     @Override public boolean      process(String opt, List<String> arguments) {
1485                         auxSource = new AndroidAuxSource();
1486                         linter = new AndroidLinter();
1487                         android = true;
1488                         return true;
1489                     }
1490                 }
1491         );
1492 
1493         options.add(
1494                 new Option() {
1495                     private final List<String> names = List.of("-manifest");
1496                     @Override public int          getArgumentCount() { return 1; }
1497                     @Override public String       getDescription() { return ""; }
1498                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1499                     @Override public List<String> getNames() { return names; }
1500                     @Override public String       getParameters() { return "<file>"; }
1501                     @Override public boolean      process(String opt, List<String> arguments) {
1502                         manifestFile = arguments.get(0);
1503                         return true;
1504                     }
1505                 }
1506         );
1507 
1508         options.add(
1509                 new Option() {
1510                     private final List<String> names = List.of("-compatconfig");
1511                     @Override public int          getArgumentCount() { return 1; }
1512                     @Override public String       getDescription() { return ""; }
1513                     @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
1514                     @Override public List<String> getNames() { return names; }
1515                     @Override public String       getParameters() { return "<config>"; }
1516                     @Override public boolean      process(String opt, List<String> arguments) {
1517                         compatConfig = arguments.get(0);
1518                         return true;
1519                     }
1520                 }
1521         );
1522 
1523         return options;
1524     }
1525 
1526     @Override
getSupportedSourceVersion()1527     public SourceVersion getSupportedSourceVersion() {
1528         return SourceVersion.latest();
1529     }
1530 
1531     @Override
run(DocletEnvironment environment)1532     public boolean run(DocletEnvironment environment) {
1533         return start(environment);
1534     }
1535 
checkLevel(int level)1536     public static boolean checkLevel(int level) {
1537         return (showLevel & level) == level;
1538     }
1539 
1540   /**
1541    * Returns true if we should parse javadoc comments,
1542    * reporting errors in the process.
1543    */
parseComments()1544   public static boolean parseComments() {
1545     return generateDocs || parseComments;
1546   }
1547 
checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv, boolean hidden)1548   public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv,
1549       boolean hidden) {
1550     if (hidden && !checkLevel(SHOW_HIDDEN)) {
1551       return false;
1552     }
1553     if (pub && checkLevel(SHOW_PUBLIC)) {
1554       return true;
1555     }
1556     if (prot && checkLevel(SHOW_PROTECTED)) {
1557       return true;
1558     }
1559     if (pkgp && checkLevel(SHOW_PACKAGE)) {
1560       return true;
1561     }
1562     if (priv && checkLevel(SHOW_PRIVATE)) {
1563       return true;
1564     }
1565     return false;
1566   }
1567 
main(String[] args)1568   public static void main(String[] args) {
1569     throw new UnsupportedOperationException("Not yet implemented");
1570   }
1571 
start(DocletEnvironment environment)1572   public static boolean start(DocletEnvironment environment) {
1573     root = new RootDocImpl(environment);
1574 
1575     // If the caller has not explicitly requested that unannotated classes and members should be
1576     // shown in the output then only show them if no annotations were provided.
1577     if (!showUnannotated && showAnnotations.isEmpty()) {
1578       showUnannotated = true;
1579     }
1580 
1581     if (!readKnownTagsFiles(knownTags, knownTagsFiles)) {
1582       return false;
1583     }
1584     if (!readManifest()) {
1585       return false;
1586     }
1587     if (!readLintBaselineFile(lintBaselineFile)) {
1588       return false;
1589     }
1590 
1591     // Set up the data structures
1592     Converter.makeInfo(root);
1593 
1594     if (generateDocs) {
1595       ClearPage.addBundledTemplateDir("assets/customizations");
1596       ClearPage.addBundledTemplateDir("assets/templates");
1597 
1598       List<ResourceLoader> resourceLoaders = new ArrayList<ResourceLoader>();
1599       List<String> templates = ClearPage.getTemplateDirs();
1600       for (String tmpl : templates) {
1601         resourceLoaders.add(new FileSystemResourceLoader(tmpl));
1602       }
1603       // If no custom template path is provided, and this is a devsite build,
1604       // then use the bundled templates-sdk/ files by default
1605       if (templates.isEmpty() && devsite) {
1606         resourceLoaders.add(new ClassResourceLoader(Doclava.class, "/assets/templates-sdk"));
1607       }
1608 
1609       templates = ClearPage.getBundledTemplateDirs();
1610       for (String tmpl : templates) {
1611           // TODO - remove commented line - it's here for debugging purposes
1612         //  resourceLoaders.add(new FileSystemResourceLoader("/Volumes/Android/master/external/doclava/res/" + tmpl));
1613         resourceLoaders.add(new ClassResourceLoader(Doclava.class, '/'+tmpl));
1614       }
1615 
1616       ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders);
1617       jSilver = new JSilver(compositeResourceLoader);
1618 
1619       if (!Doclava.readTemplateSettings()) {
1620         return false;
1621       }
1622 
1623       // if requested, only generate the navtree for ds use-case
1624       if (NAVTREE_ONLY) {
1625         if (AT_LINKS_NAVTREE) {
1626           AtLinksNavTree.writeAtLinksNavTree(javadocDir);
1627         } else if (yamlV2) {
1628           // Generate DAC-formatted left-nav for devsite
1629           NavTree.writeYamlTree2(javadocDir, yamlNavFile);
1630         } else {
1631           // This shouldn't happen; this is the legacy DAC left nav file
1632           NavTree.writeNavTree(javadocDir, "");
1633         }
1634         return true;
1635       }
1636 
1637       // don't do ref doc tasks in devsite static-only builds
1638       if (!DEVSITE_STATIC_ONLY) {
1639         // Load additional data structures from federated sites.
1640         for(FederatedSite site : federationTagger.getSites()) {
1641           Converter.addApiInfo(site.apiInfo());
1642         }
1643 
1644         // Apply @since tags from the XML file
1645         sinceTagger.tagAll(Converter.rootClasses());
1646 
1647         // Apply @artifact tags from the XML file
1648         artifactTagger.tagAll(Converter.rootClasses());
1649 
1650         // Apply details of federated documentation
1651         federationTagger.tagAll(Converter.rootClasses());
1652 
1653         // Files for proofreading
1654         if (proofreadFile != null) {
1655           Proofread.initProofread(proofreadFile);
1656         }
1657         if (todoFile != null) {
1658           TodoFile.writeTodoFile(todoFile);
1659         }
1660 
1661         if (samplesRef) {
1662           // always write samples without offlineMode behaviors
1663           writeSamples(false, sampleCodes, SORT_BY_NAV_GROUPS);
1664         }
1665       }
1666       if (!referenceOnly) {
1667         // HTML2 Pages -- Generate Pages from optional secondary dir
1668         if (!inputPathHtmlDir2.isEmpty()) {
1669           if (!outputPathHtmlDir2.isEmpty()) {
1670             ClearPage.outputDir = outputPathBase + "/" + outputPathHtmlDir2;
1671           }
1672           ClearPage.htmlDirs = inputPathHtmlDir2;
1673           writeHTMLPages();
1674           ClearPage.htmlDirs = inputPathHtmlDirs;
1675         }
1676 
1677         // HTML Pages
1678         if (!ClearPage.htmlDirs.isEmpty()) {
1679           ClearPage.htmlDirs = inputPathHtmlDirs;
1680           if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
1681             ClearPage.outputDir = outputPathHtmlDirs + "/en/";
1682           } else {
1683             ClearPage.outputDir = outputPathHtmlDirs;
1684           }
1685           writeHTMLPages();
1686         }
1687       }
1688 
1689       writeResources();
1690 
1691       writeAssets();
1692 
1693       // don't do ref doc tasks in devsite static-only builds
1694       if (!DEVSITE_STATIC_ONLY) {
1695         // Navigation tree
1696         String refPrefix = new String();
1697         if(gmsRef){
1698           refPrefix = "gms-";
1699         } else if(gcmRef){
1700           refPrefix = "gcm-";
1701         }
1702 
1703         // Packages Pages
1704         writePackages(refPrefix + "packages" + htmlExtension);
1705 
1706         // Classes
1707         writeClassLists();
1708         writeClasses();
1709         writeHierarchy();
1710         writeCompatConfig();
1711         // writeKeywords();
1712 
1713         // Write yaml tree.
1714         if (yamlNavFile != null) {
1715           if (yamlV2) {
1716             // Generate DAC-formatted left-nav for devsite
1717             NavTree.writeYamlTree2(javadocDir, yamlNavFile);
1718           } else {
1719             // Generate legacy devsite left-nav (shows sub-classes nested under parent class)
1720             NavTree.writeYamlTree(javadocDir, yamlNavFile);
1721           }
1722         } else {
1723           // This shouldn't happen; this is the legacy DAC left nav file
1724           NavTree.writeNavTree(javadocDir, refPrefix);
1725         }
1726 
1727         // Lists for JavaScript
1728         writeLists();
1729         if (keepListFile != null) {
1730           writeKeepList(keepListFile);
1731         }
1732 
1733         Proofread.finishProofread(proofreadFile);
1734 
1735         if (sdkValuePath != null) {
1736           writeSdkValues(sdkValuePath);
1737         }
1738       }
1739       // Write metadata for all processed files to jd_lists_unified in out dir
1740       if (!sTaglist.isEmpty()) {
1741         PageMetadata.WriteListByLang(sTaglist);
1742         // For devsite (ds) reference only, write samples_metadata to out dir
1743         if ((devsite) && (!DEVSITE_STATIC_ONLY)) {
1744           PageMetadata.WriteSamplesListByLang(sTaglist);
1745         }
1746       }
1747     }
1748 
1749     // Stubs
1750     if (stubsDir != null || apiFile != null || dexApiFile != null || proguardFile != null
1751         || removedApiFile != null || removedDexApiFile != null || exactApiFile != null
1752         || privateApiFile != null || privateDexApiFile != null || apiMappingFile != null) {
1753       Stubs.writeStubsAndApi(stubsDir, apiFile, dexApiFile, proguardFile, removedApiFile,
1754           removedDexApiFile, exactApiFile, privateApiFile, privateDexApiFile, apiMappingFile,
1755           stubPackages, stubImportPackages, stubSourceOnly, keepStubComments);
1756     }
1757 
1758     Errors.printErrors();
1759 
1760     return !Errors.hadError;
1761   }
1762 
writeIndex(String dir)1763   private static void writeIndex(String dir) {
1764     Data data = makeHDF();
1765     ClearPage.write(data, "index.cs", dir + "index" + htmlExtension);
1766   }
1767 
readTemplateSettings()1768   private static boolean readTemplateSettings() {
1769     Data data = makeHDF();
1770 
1771     // The .html extension is hard-coded in several .cs files,
1772     // and so you cannot currently set it as a property.
1773     htmlExtension = ".html";
1774     // htmlExtension = data.getValue("template.extension", ".html");
1775     int i = 0;
1776     while (true) {
1777       String k = data.getValue("template.escape." + i + ".key", "");
1778       String v = data.getValue("template.escape." + i + ".value", "");
1779       if ("".equals(k)) {
1780         break;
1781       }
1782       if (k.length() != 1) {
1783         System.err.println("template.escape." + i + ".key must have a length of 1: " + k);
1784         return false;
1785       }
1786       escapeChars.put(k.charAt(0), v);
1787       i++;
1788     }
1789     return true;
1790   }
1791 
readKnownTagsFiles(HashSet<String> knownTags, ArrayList<String> knownTagsFiles)1792     private static boolean readKnownTagsFiles(HashSet<String> knownTags,
1793             ArrayList<String> knownTagsFiles) {
1794         for (String fn: knownTagsFiles) {
1795            BufferedReader in = null;
1796            try {
1797                in = new BufferedReader(new FileReader(fn));
1798                int lineno = 0;
1799                boolean fail = false;
1800                while (true) {
1801                    lineno++;
1802                    String line = in.readLine();
1803                    if (line == null) {
1804                        break;
1805                    }
1806                    line = line.trim();
1807                    if (line.length() == 0) {
1808                        continue;
1809                    } else if (line.charAt(0) == '#') {
1810                        continue;
1811                    }
1812                    String[] words = line.split("\\s+", 2);
1813                    if (words.length == 2) {
1814                        if (words[1].charAt(0) != '#') {
1815                            System.err.println(fn + ":" + lineno
1816                                    + ": Only one tag allowed per line: " + line);
1817                            fail = true;
1818                            continue;
1819                        }
1820                    }
1821                    knownTags.add(words[0]);
1822                }
1823                if (fail) {
1824                    return false;
1825                }
1826            } catch (IOException ex) {
1827                System.err.println("Error reading file: " + fn + " (" + ex.getMessage() + ")");
1828                return false;
1829            } finally {
1830                if (in != null) {
1831                    try {
1832                        in.close();
1833                    } catch (IOException e) {
1834                    }
1835                }
1836            }
1837         }
1838         return true;
1839     }
1840 
readLintBaselineFile(String lintBaselineFile)1841     private static boolean readLintBaselineFile(String lintBaselineFile) {
1842         if (lintBaselineFile == null) {
1843           return true;
1844         }
1845         try (BufferedReader reader = new BufferedReader(new FileReader(lintBaselineFile))) {
1846             List<LintBaselineEntry> baseline =
1847                     reader.lines()
1848                         .filter(l -> !l.trim().isEmpty() && !l.startsWith("//"))
1849                         .map(ErrorMessage::parse)
1850                         .filter(e -> e != null)
1851                         .collect(toList());
1852             Errors.setLintBaseline(baseline);
1853             return true;
1854         } catch (IOException exception) {
1855             exception.printStackTrace(System.err);
1856             return false;
1857         }
1858     }
1859 
readManifest()1860   private static boolean readManifest() {
1861     manifestPermissions.clear();
1862     if (manifestFile == null) {
1863       return true;
1864     }
1865     try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(manifestFile));
1866         ByteArrayOutputStream out = new ByteArrayOutputStream()) {
1867       byte[] buffer = new byte[1024];
1868       int count;
1869       while ((count = in.read(buffer)) != -1) {
1870         out.write(buffer, 0, count);
1871       }
1872       final Matcher m = Pattern.compile("(?s)<permission "
1873           + "[^>]*android:name=\"([^\">]+)\""
1874           + "[^>]*android:protectionLevel=\"([^\">]+)\"").matcher(out.toString());
1875       while (m.find()) {
1876         manifestPermissions.put(m.group(1), m.group(2));
1877       }
1878     } catch (IOException e) {
1879       Errors.error(Errors.PARSE_ERROR, (SourcePositionInfo) null,
1880           "Failed to parse " + manifestFile + ": " + e);
1881       return false;
1882     }
1883     return true;
1884   }
1885 
escape(String s)1886   public static String escape(String s) {
1887     if (escapeChars.size() == 0) {
1888       return s;
1889     }
1890     StringBuffer b = null;
1891     int begin = 0;
1892     final int N = s.length();
1893     for (int i = 0; i < N; i++) {
1894       char c = s.charAt(i);
1895       String mapped = escapeChars.get(c);
1896       if (mapped != null) {
1897         if (b == null) {
1898           b = new StringBuffer(s.length() + mapped.length());
1899         }
1900         if (begin != i) {
1901           b.append(s.substring(begin, i));
1902         }
1903         b.append(mapped);
1904         begin = i + 1;
1905       }
1906     }
1907     if (b != null) {
1908       if (begin != N) {
1909         b.append(s.substring(begin, N));
1910       }
1911       return b.toString();
1912     }
1913     return s;
1914   }
1915 
setPageTitle(Data data, String title)1916   public static void setPageTitle(Data data, String title) {
1917     String s = title;
1918     if (Doclava.title.length() > 0) {
1919       s += " - " + Doclava.title;
1920     }
1921     data.setValue("page.title", s);
1922   }
1923 
makeHDF()1924   public static Data makeHDF() {
1925     Data data = jSilver.createData();
1926 
1927     for (String[] p : mHDFData) {
1928       data.setValue(p[0], p[1]);
1929     }
1930 
1931     return data;
1932   }
1933 
makePackageHDF()1934   public static Data makePackageHDF() {
1935     Data data = makeHDF();
1936     Collection<ClassInfo> classes = Converter.rootClasses();
1937 
1938     SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
1939     for (ClassInfo cl : classes) {
1940       PackageInfo pkg = cl.containingPackage();
1941       String name;
1942       if (pkg == null) {
1943         name = "";
1944       } else {
1945         name = pkg.name();
1946       }
1947       sorted.put(name, pkg);
1948     }
1949 
1950     int i = 0;
1951     for (Map.Entry<String, PackageInfo> entry : sorted.entrySet()) {
1952       String s = entry.getKey();
1953       PackageInfo pkg = entry.getValue();
1954 
1955       if (pkg.isHiddenOrRemoved()) {
1956         continue;
1957       }
1958       boolean allHiddenOrRemoved = true;
1959       int pass = 0;
1960       ClassInfo[] classesToCheck = null;
1961       while (pass < 6) {
1962         switch (pass) {
1963           case 0:
1964             classesToCheck = pkg.ordinaryClasses();
1965             break;
1966           case 1:
1967             classesToCheck = pkg.enums();
1968             break;
1969           case 2:
1970             classesToCheck = pkg.errors();
1971             break;
1972           case 3:
1973             classesToCheck = pkg.exceptions();
1974             break;
1975           case 4:
1976             classesToCheck = pkg.interfaces();
1977             break;
1978           case 5:
1979             classesToCheck = pkg.annotations();
1980             break;
1981           default:
1982             System.err.println("Error reading package: " + pkg.name());
1983             break;
1984         }
1985         for (ClassInfo cl : classesToCheck) {
1986           if (!cl.isHiddenOrRemoved()) {
1987             allHiddenOrRemoved = false;
1988             break;
1989           }
1990         }
1991         if (!allHiddenOrRemoved) {
1992           break;
1993         }
1994         pass++;
1995       }
1996       if (allHiddenOrRemoved) {
1997         continue;
1998       }
1999       if(gmsRef){
2000           data.setValue("reference.gms", "true");
2001       } else if(gcmRef){
2002           data.setValue("reference.gcm", "true");
2003       }
2004       data.setValue("reference", "1");
2005       if (METALAVA_API_SINCE) {
2006         data.setValue("reference.apilevels", (pkg.getSince() != null) ? "1" : "0");
2007       } else {
2008         data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0");
2009       }
2010       data.setValue("reference.artifacts", artifactTagger.hasArtifacts() ? "1" : "0");
2011       data.setValue("docs.packages." + i + ".name", s);
2012       data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
2013       data.setValue("docs.packages." + i + ".since", pkg.getSince());
2014       TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags());
2015       i++;
2016     }
2017 
2018     sinceTagger.writeVersionNames(data);
2019     return data;
2020   }
2021 
writeDirectory(File dir, String relative, JSilver js)2022   private static void writeDirectory(File dir, String relative, JSilver js) {
2023     File[] files = dir.listFiles();
2024     int i, count = files.length;
2025     for (i = 0; i < count; i++) {
2026       File f = files[i];
2027       if (f.isFile()) {
2028         String templ = relative + f.getName();
2029         int len = templ.length();
2030         if (len > 3 && ".cs".equals(templ.substring(len - 3))) {
2031           Data data = makePackageHDF();
2032           String filename = templ.substring(0, len - 3) + htmlExtension;
2033           ClearPage.write(data, templ, filename, js);
2034         } else if (len > 3 && ".jd".equals(templ.substring(len - 3))) {
2035           Data data = makePackageHDF();
2036           String filename = templ.substring(0, len - 3) + htmlExtension;
2037           DocFile.writePage(f.getAbsolutePath(), relative, filename, data);
2038         } else if(!f.getName().equals(".DS_Store")){
2039               Data data = makeHDF();
2040               String hdfValue = data.getValue("sac") == null ? "" : data.getValue("sac");
2041               boolean allowExcepted = hdfValue.equals("true") ? true : false;
2042               boolean append = false;
2043               ClearPage.copyFile(allowExcepted, f, templ, append);
2044         }
2045       } else if (f.isDirectory()) {
2046         writeDirectory(f, relative + f.getName() + "/", js);
2047       }
2048     }
2049   }
2050 
writeHTMLPages()2051   public static void writeHTMLPages() {
2052     for (String htmlDir : ClearPage.htmlDirs) {
2053       File f = new File(htmlDir);
2054       if (!f.isDirectory()) {
2055         System.err.println("htmlDir not a directory: " + htmlDir);
2056         continue;
2057       }
2058 
2059       ResourceLoader loader = new FileSystemResourceLoader(f);
2060       JSilver js = new JSilver(loader);
2061       writeDirectory(f, "", js);
2062     }
2063   }
2064 
2065   /* copy files supplied by the -resourcesdir flag */
writeResources()2066   public static void writeResources() {
2067     if (inputPathResourcesDir != null && !inputPathResourcesDir.isEmpty()) {
2068       try {
2069         File f = new File(inputPathResourcesDir);
2070         if (!f.isDirectory()) {
2071           System.err.println("resourcesdir is not a directory: " + inputPathResourcesDir);
2072           return;
2073         }
2074 
2075         ResourceLoader loader = new FileSystemResourceLoader(f);
2076         JSilver js = new JSilver(loader);
2077         writeDirectory(f, outputPathResourcesDir, js);
2078       } catch(Exception e) {
2079         System.err.println("Could not copy resourcesdir: " + e);
2080       }
2081     }
2082   }
2083 
writeAssets()2084   public static void writeAssets() {
2085     if (!includeAssets) return;
2086     JarFile thisJar = JarUtils.jarForClass(Doclava.class, null);
2087     if ((thisJar != null) && (includeDefaultAssets)) {
2088       try {
2089         List<String> templateDirs = ClearPage.getBundledTemplateDirs();
2090         for (String templateDir : templateDirs) {
2091           String assetsDir = templateDir + "/assets";
2092           JarUtils.copyResourcesToDirectory(thisJar, assetsDir, ClearPage.outputDir + "/assets");
2093         }
2094       } catch (IOException e) {
2095         System.err.println("Error copying assets directory.");
2096         e.printStackTrace();
2097         return;
2098       }
2099     }
2100 
2101     //write the project-specific assets
2102     List<String> templateDirs = ClearPage.getTemplateDirs();
2103     for (String templateDir : templateDirs) {
2104       File assets = new File(templateDir + "/assets");
2105       if (assets.isDirectory()) {
2106         writeDirectory(assets, "assets/", null);
2107       }
2108     }
2109 
2110     // Create the timestamp.js file based on .cs file
2111     Data timedata = Doclava.makeHDF();
2112     ClearPage.write(timedata, "timestamp.cs", "timestamp.js");
2113   }
2114 
2115   /** Go through the docs and generate meta-data about each
2116       page to use in search suggestions */
writeLists()2117   public static void writeLists() {
2118 
2119     // Write the lists for API references
2120     Data data = makeHDF();
2121 
2122     Collection<ClassInfo> classes = Converter.rootClasses();
2123 
2124     SortedMap<String, Object> sorted = new TreeMap<String, Object>();
2125     for (ClassInfo cl : classes) {
2126       if (cl.isHiddenOrRemoved()) {
2127         continue;
2128       }
2129       sorted.put(cl.qualifiedName(), cl);
2130       PackageInfo pkg = cl.containingPackage();
2131       String name;
2132       if (pkg == null) {
2133         name = "";
2134       } else {
2135         name = pkg.name();
2136       }
2137       sorted.put(name, pkg);
2138     }
2139 
2140     int i = 0;
2141     String listDir = javadocDir;
2142     if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
2143       if (libraryRoot != null) {
2144         listDir = listDir + libraryRoot;
2145       }
2146     }
2147     for (String s : sorted.keySet()) {
2148       data.setValue("docs.pages." + i + ".id", "" + i);
2149       data.setValue("docs.pages." + i + ".label", s);
2150 
2151       Object o = sorted.get(s);
2152       if (o instanceof PackageInfo) {
2153         PackageInfo pkg = (PackageInfo) o;
2154         data.setValue("docs.pages." + i + ".link", pkg.htmlPage());
2155         data.setValue("docs.pages." + i + ".type", "package");
2156         data.setValue("docs.pages." + i + ".deprecated", pkg.isDeprecated() ? "true" : "false");
2157       } else if (o instanceof ClassInfo) {
2158         ClassInfo cl = (ClassInfo) o;
2159         data.setValue("docs.pages." + i + ".link", cl.htmlPage());
2160         data.setValue("docs.pages." + i + ".type", "class");
2161         data.setValue("docs.pages." + i + ".deprecated", cl.isDeprecated() ? "true" : "false");
2162       }
2163       i++;
2164     }
2165     ClearPage.write(data, "lists.cs", listDir + "lists.js");
2166 
2167 
2168     // Write the lists for JD documents (if there are HTML directories to process)
2169     // Skip this for devsite builds
2170     if ((inputPathHtmlDirs.size() > 0) && (!devsite)) {
2171       Data jddata = makeHDF();
2172       Iterator counter = new Iterator();
2173       for (String htmlDir : inputPathHtmlDirs) {
2174         File dir = new File(htmlDir);
2175         if (!dir.isDirectory()) {
2176           continue;
2177         }
2178         writeJdDirList(dir, jddata, counter);
2179       }
2180       ClearPage.write(jddata, "jd_lists.cs", javadocDir + "jd_lists.js");
2181     }
2182   }
2183 
2184   private static class Iterator {
2185     int i = 0;
2186   }
2187 
2188   /** Write meta-data for a JD file, used for search suggestions */
writeJdDirList(File dir, Data data, Iterator counter)2189   private static void writeJdDirList(File dir, Data data, Iterator counter) {
2190     File[] files = dir.listFiles();
2191     int i, count = files.length;
2192     // Loop all files in given directory
2193     for (i = 0; i < count; i++) {
2194       File f = files[i];
2195       if (f.isFile()) {
2196         String filePath = f.getAbsolutePath();
2197         String templ = f.getName();
2198         int len = templ.length();
2199         // If it's a .jd file we want to process
2200         if (len > 3 && ".jd".equals(templ.substring(len - 3))) {
2201           // remove the directories below the site root
2202           String webPath = filePath.substring(filePath.indexOf("docs/html/") + 10,
2203               filePath.length());
2204           // replace .jd with .html
2205           webPath = webPath.substring(0, webPath.length() - 3) + htmlExtension;
2206           // Parse the .jd file for properties data at top of page
2207           Data hdf = Doclava.makeHDF();
2208           String filedata = DocFile.readFile(filePath);
2209           Matcher lines = DocFile.LINE.matcher(filedata);
2210           String line = null;
2211           // Get each line to add the key-value to hdf
2212           while (lines.find()) {
2213             line = lines.group(1);
2214             if (line.length() > 0) {
2215               // Stop when we hit the body
2216               if (line.equals("@jd:body")) {
2217                 break;
2218               }
2219               Matcher prop = DocFile.PROP.matcher(line);
2220               if (prop.matches()) {
2221                 String key = prop.group(1);
2222                 String value = prop.group(2);
2223                 hdf.setValue(key, value);
2224               } else {
2225                 break;
2226               }
2227             }
2228           } // done gathering page properties
2229 
2230           // Insert the goods into HDF data (title, link, tags, type)
2231           String title = hdf.getValue("page.title", "");
2232           title = title.replaceAll("\"", "'");
2233           // if there's a <span> in the title, get rid of it
2234           if (title.indexOf("<span") != -1) {
2235             String[] splitTitle = title.split("<span(.*?)</span>");
2236             title = splitTitle[0];
2237             for (int j = 1; j < splitTitle.length; j++) {
2238               title.concat(splitTitle[j]);
2239             }
2240           }
2241 
2242           StringBuilder tags =  new StringBuilder();
2243           String tagsList = hdf.getValue("page.tags", "");
2244           if (!tagsList.equals("")) {
2245             tagsList = tagsList.replaceAll("\"", "");
2246             String[] tagParts = tagsList.split(",");
2247             for (int iter = 0; iter < tagParts.length; iter++) {
2248               tags.append("\"");
2249               tags.append(tagParts[iter].trim());
2250               tags.append("\"");
2251               if (iter < tagParts.length - 1) {
2252                 tags.append(",");
2253               }
2254             }
2255           }
2256 
2257           String dirName = (webPath.indexOf("/") != -1)
2258                   ? webPath.substring(0, webPath.indexOf("/")) : "";
2259 
2260           if (!"".equals(title) &&
2261               !"intl".equals(dirName) &&
2262               !hdf.getBooleanValue("excludeFromSuggestions")) {
2263             data.setValue("docs.pages." + counter.i + ".label", title);
2264             data.setValue("docs.pages." + counter.i + ".link", webPath);
2265             data.setValue("docs.pages." + counter.i + ".tags", tags.toString());
2266             data.setValue("docs.pages." + counter.i + ".type", dirName);
2267             counter.i++;
2268           }
2269         }
2270       } else if (f.isDirectory()) {
2271         writeJdDirList(f, data, counter);
2272       }
2273     }
2274   }
2275 
cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable)2276   public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) {
2277     if (!notStrippable.add(cl)) {
2278       // slight optimization: if it already contains cl, it already contains
2279       // all of cl's parents
2280       return;
2281     }
2282     ClassInfo supr = cl.superclass();
2283     if (supr != null) {
2284       cantStripThis(supr, notStrippable);
2285     }
2286     for (ClassInfo iface : cl.interfaces()) {
2287       cantStripThis(iface, notStrippable);
2288     }
2289   }
2290 
getPrintableName(ClassInfo cl)2291   private static String getPrintableName(ClassInfo cl) {
2292     ClassInfo containingClass = cl.containingClass();
2293     if (containingClass != null) {
2294       // This is an inner class.
2295       String baseName = cl.name();
2296       baseName = baseName.substring(baseName.lastIndexOf('.') + 1);
2297       return getPrintableName(containingClass) + '$' + baseName;
2298     }
2299     return cl.qualifiedName();
2300   }
2301 
2302   /**
2303    * Writes the list of classes that must be present in order to provide the non-hidden APIs known
2304    * to javadoc.
2305    *
2306    * @param filename the path to the file to write the list to
2307    */
writeKeepList(String filename)2308   public static void writeKeepList(String filename) {
2309     HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
2310     Collection<ClassInfo> all = Converter.allClasses().stream().sorted(ClassInfo.comparator)
2311         .collect(Collectors.toList());
2312 
2313     // If a class is public and not hidden, then it and everything it derives
2314     // from cannot be stripped. Otherwise we can strip it.
2315     for (ClassInfo cl : all) {
2316       if (cl.isPublic() && !cl.isHiddenOrRemoved()) {
2317         cantStripThis(cl, notStrippable);
2318       }
2319     }
2320     PrintStream stream = null;
2321     try {
2322       stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(filename)));
2323       for (ClassInfo cl : notStrippable) {
2324         stream.println(getPrintableName(cl));
2325       }
2326     } catch (FileNotFoundException e) {
2327       System.err.println("error writing file: " + filename);
2328     } finally {
2329       if (stream != null) {
2330         stream.close();
2331       }
2332     }
2333   }
2334 
2335   private static PackageInfo[] sVisiblePackages = null;
2336 
choosePackages()2337   public static PackageInfo[] choosePackages() {
2338     if (sVisiblePackages != null) {
2339       return sVisiblePackages;
2340     }
2341 
2342     Collection<ClassInfo> classes = Converter.rootClasses();
2343     SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
2344     for (ClassInfo cl : classes) {
2345       PackageInfo pkg = cl.containingPackage();
2346       String name;
2347       if (pkg == null) {
2348         name = "";
2349       } else {
2350         name = pkg.name();
2351       }
2352       sorted.put(name, pkg);
2353     }
2354 
2355     ArrayList<PackageInfo> result = new ArrayList<PackageInfo>();
2356 
2357     for (String s : sorted.keySet()) {
2358       PackageInfo pkg = sorted.get(s);
2359 
2360       if (pkg.isHiddenOrRemoved()) {
2361         continue;
2362       }
2363 
2364       boolean allHiddenOrRemoved = true;
2365       int pass = 0;
2366       ClassInfo[] classesToCheck = null;
2367       while (pass < 6) {
2368         switch (pass) {
2369           case 0:
2370             classesToCheck = pkg.ordinaryClasses();
2371             break;
2372           case 1:
2373             classesToCheck = pkg.enums();
2374             break;
2375           case 2:
2376             classesToCheck = pkg.errors();
2377             break;
2378           case 3:
2379             classesToCheck = pkg.exceptions();
2380             break;
2381           case 4:
2382             classesToCheck = pkg.interfaces();
2383             break;
2384           case 5:
2385             classesToCheck = pkg.annotations();
2386             break;
2387           default:
2388             System.err.println("Error reading package: " + pkg.name());
2389             break;
2390         }
2391         for (ClassInfo cl : classesToCheck) {
2392           if (!cl.isHiddenOrRemoved()) {
2393             allHiddenOrRemoved = false;
2394             break;
2395           }
2396         }
2397         if (!allHiddenOrRemoved) {
2398           break;
2399         }
2400         pass++;
2401       }
2402       if (allHiddenOrRemoved) {
2403         continue;
2404       }
2405 
2406       result.add(pkg);
2407     }
2408 
2409     sVisiblePackages = result.toArray(new PackageInfo[result.size()]);
2410     return sVisiblePackages;
2411   }
2412 
writePackages(String filename)2413   public static void writePackages(String filename) {
2414     Data data = makePackageHDF();
2415 
2416     int i = 0;
2417     for (PackageInfo pkg : choosePackages()) {
2418       writePackage(pkg);
2419 
2420       data.setValue("docs.packages." + i + ".name", pkg.name());
2421       data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
2422       TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags());
2423 
2424       i++;
2425     }
2426 
2427     setPageTitle(data, "Package Index");
2428 
2429     TagInfo.makeHDF(data, "root.descr", Converter.convertTags(root.inlineTags(), null));
2430 
2431     String packageDir = javadocDir;
2432     if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
2433       if (libraryRoot != null) {
2434         packageDir = packageDir + libraryRoot;
2435       }
2436     }
2437     data.setValue("page.not-api", "true");
2438     ClearPage.write(data, "packages.cs", packageDir + filename);
2439     ClearPage.write(data, "package-list.cs", packageDir + "package-list");
2440 
2441     Proofread.writePackages(filename, Converter.convertTags(root.inlineTags(), null));
2442   }
2443 
writePackage(PackageInfo pkg)2444   public static void writePackage(PackageInfo pkg) {
2445     // these this and the description are in the same directory,
2446     // so it's okay
2447     Data data = makePackageHDF();
2448 
2449     String name = pkg.name();
2450 
2451     data.setValue("package.name", name);
2452     data.setValue("package.since", pkg.getSince());
2453     data.setValue("package.descr", "...description...");
2454     pkg.setFederatedReferences(data, "package");
2455 
2456     makeClassListHDF(data, "package.annotations", ClassInfo.sortByName(pkg.annotations()));
2457     makeClassListHDF(data, "package.interfaces", ClassInfo.sortByName(pkg.interfaces()));
2458     makeClassListHDF(data, "package.classes", ClassInfo.sortByName(pkg.ordinaryClasses()));
2459     makeClassListHDF(data, "package.enums", ClassInfo.sortByName(pkg.enums()));
2460     makeClassListHDF(data, "package.exceptions", ClassInfo.sortByName(pkg.exceptions()));
2461     makeClassListHDF(data, "package.errors", ClassInfo.sortByName(pkg.errors()));
2462     TagInfo.makeHDF(data, "package.shortDescr", pkg.firstSentenceTags());
2463     TagInfo.makeHDF(data, "package.descr", pkg.inlineTags());
2464 
2465     String filename = pkg.htmlPage();
2466     setPageTitle(data, name);
2467     ClearPage.write(data, "package.cs", filename);
2468 
2469     Proofread.writePackage(filename, pkg.inlineTags());
2470   }
2471 
writeClassLists()2472   public static void writeClassLists() {
2473     int i;
2474     Data data = makePackageHDF();
2475 
2476     ClassInfo[] classes = PackageInfo.filterHiddenAndRemoved(
2477         Converter.convertClasses(root.classes()));
2478     if (classes.length == 0) {
2479       return;
2480     }
2481 
2482     Sorter[] sorted = new Sorter[classes.length];
2483     for (i = 0; i < sorted.length; i++) {
2484       ClassInfo cl = classes[i];
2485       String name = cl.name();
2486       sorted[i] = new Sorter(name, cl);
2487     }
2488 
2489     Arrays.sort(sorted);
2490 
2491     // make a pass and resolve ones that have the same name
2492     int firstMatch = 0;
2493     String lastName = sorted[0].label;
2494     for (i = 1; i < sorted.length; i++) {
2495       String s = sorted[i].label;
2496       if (!lastName.equals(s)) {
2497         if (firstMatch != i - 1) {
2498           // there were duplicates
2499           for (int j = firstMatch; j < i; j++) {
2500             PackageInfo pkg = ((ClassInfo) sorted[j].data).containingPackage();
2501             if (pkg != null) {
2502               sorted[j].label = sorted[j].label + " (" + pkg.name() + ")";
2503             }
2504           }
2505         }
2506         firstMatch = i;
2507         lastName = s;
2508       }
2509     }
2510 
2511     // and sort again
2512     Arrays.sort(sorted);
2513 
2514     for (i = 0; i < sorted.length; i++) {
2515       String s = sorted[i].label;
2516       ClassInfo cl = (ClassInfo) sorted[i].data;
2517       char first = Character.toUpperCase(s.charAt(0));
2518       cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i);
2519     }
2520 
2521     String packageDir = javadocDir;
2522     if (USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
2523       if (libraryRoot != null) {
2524         packageDir = packageDir + libraryRoot;
2525       }
2526     }
2527 
2528     data.setValue("page.not-api", "true");
2529     setPageTitle(data, "Class Index");
2530     ClearPage.write(data, "classes.cs", packageDir + "classes" + htmlExtension);
2531 
2532     if (!devsite) {
2533       // Index page redirects to the classes.html page, so use the same directory
2534       // This page is not needed for devsite builds, which should instead use _redirects.yaml
2535       writeIndex(packageDir);
2536     }
2537   }
2538 
2539   // we use the word keywords because "index" means something else in html land
2540   // the user only ever sees the word index
2541   /*
2542    * public static void writeKeywords() { ArrayList<KeywordEntry> keywords = new
2543    * ArrayList<KeywordEntry>();
2544    *
2545    * ClassInfo[] classes = PackageInfo.filterHiddenAndRemoved(Converter.convertClasses(root.classes()));
2546    *
2547    * for (ClassInfo cl: classes) { cl.makeKeywordEntries(keywords); }
2548    *
2549    * HDF data = makeHDF();
2550    *
2551    * Collections.sort(keywords);
2552    *
2553    * int i=0; for (KeywordEntry entry: keywords) { String base = "keywords." + entry.firstChar() +
2554    * "." + i; entry.makeHDF(data, base); i++; }
2555    *
2556    * setPageTitle(data, "Index"); ClearPage.write(data, "keywords.cs", javadocDir + "keywords" +
2557    * htmlExtension); }
2558    */
2559 
writeHierarchy()2560   public static void writeHierarchy() {
2561     Collection<ClassInfo> classes = Converter.rootClasses();
2562     ArrayList<ClassInfo> info = new ArrayList<ClassInfo>();
2563     for (ClassInfo cl : classes) {
2564       if (!cl.isHiddenOrRemoved()) {
2565         info.add(cl);
2566       }
2567     }
2568     Data data = makePackageHDF();
2569     Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()]));
2570     setPageTitle(data, "Class Hierarchy");
2571     ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension);
2572   }
2573 
writeClasses()2574   public static void writeClasses() {
2575     Collection<ClassInfo> classes = Converter.rootClasses();
2576 
2577     for (ClassInfo cl : classes) {
2578       Data data = makePackageHDF();
2579       if (!cl.isHiddenOrRemoved()) {
2580         writeClass(cl, data);
2581       }
2582     }
2583   }
2584 
writeClass(ClassInfo cl, Data data)2585   public static void writeClass(ClassInfo cl, Data data) {
2586     cl.makeHDF(data);
2587     setPageTitle(data, cl.name());
2588     String outfile = cl.htmlPage();
2589     ClearPage.write(data, "class.cs", outfile);
2590     Proofread.writeClass(cl.htmlPage(), cl);
2591   }
2592 
makeClassListHDF(Data data, String base, ClassInfo[] classes)2593   public static void makeClassListHDF(Data data, String base, ClassInfo[] classes) {
2594     for (int i = 0; i < classes.length; i++) {
2595       ClassInfo cl = classes[i];
2596       if (!cl.isHiddenOrRemoved()) {
2597         cl.makeShortDescrHDF(data, base + "." + i);
2598       }
2599     }
2600   }
2601 
linkTarget(String source, String target)2602   public static String linkTarget(String source, String target) {
2603     String[] src = source.split("/");
2604     String[] tgt = target.split("/");
2605 
2606     int srclen = src.length;
2607     int tgtlen = tgt.length;
2608 
2609     int same = 0;
2610     while (same < (srclen - 1) && same < (tgtlen - 1) && (src[same].equals(tgt[same]))) {
2611       same++;
2612     }
2613 
2614     String s = "";
2615 
2616     int up = srclen - same - 1;
2617     for (int i = 0; i < up; i++) {
2618       s += "../";
2619     }
2620 
2621 
2622     int N = tgtlen - 1;
2623     for (int i = same; i < N; i++) {
2624       s += tgt[i] + '/';
2625     }
2626     s += tgt[tgtlen - 1];
2627 
2628     return s;
2629   }
2630 
2631   /**
2632    * Returns true if the given element has an @hide, @removed or @pending annotation.
2633    */
hasHideOrRemovedAnnotation(Doc doc)2634   private static boolean hasHideOrRemovedAnnotation(Doc doc) {
2635     String comment = doc.getRawCommentText();
2636     return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -1 ||
2637         comment.indexOf("@removed") != -1;
2638   }
2639 
2640   /**
2641    * Returns true if the given element is hidden.
2642    */
isHiddenOrRemoved(Doc doc)2643   private static boolean isHiddenOrRemoved(Doc doc) {
2644     // Methods, fields, constructors.
2645     if (doc instanceof MemberDoc) {
2646       return hasHideOrRemovedAnnotation(doc);
2647     }
2648 
2649     // Classes, interfaces, enums, annotation types.
2650     if (doc instanceof ClassDoc) {
2651       ClassDoc classDoc = (ClassDoc) doc;
2652 
2653       // Check the containing package.
2654       if (hasHideOrRemovedAnnotation(classDoc.containingPackage())) {
2655         return true;
2656       }
2657 
2658       // Check the class doc and containing class docs if this is a
2659       // nested class.
2660       ClassDoc current = classDoc;
2661       do {
2662         if (hasHideOrRemovedAnnotation(current)) {
2663           return true;
2664         }
2665 
2666         current = current.containingClass();
2667       } while (current != null);
2668     }
2669 
2670     return false;
2671   }
2672 
2673   /**
2674    * Filters out hidden and removed elements.
2675    */
filterHiddenAndRemoved(Object o, Class<?> expected)2676   private static Object filterHiddenAndRemoved(Object o, Class<?> expected) {
2677     if (o == null) {
2678       return null;
2679     }
2680 
2681     Class type = o.getClass();
2682     if (type.getName().startsWith("com.sun.")) {
2683       // TODO: Implement interfaces from superclasses, too.
2684       return Proxy
2685           .newProxyInstance(type.getClassLoader(), type.getInterfaces(), new HideHandler(o));
2686     } else if (o instanceof Object[]) {
2687       Class<?> componentType = expected.getComponentType();
2688       Object[] array = (Object[]) o;
2689       List<Object> list = new ArrayList<Object>(array.length);
2690       for (Object entry : array) {
2691         if ((entry instanceof Doc) && isHiddenOrRemoved((Doc) entry)) {
2692           continue;
2693         }
2694         list.add(filterHiddenAndRemoved(entry, componentType));
2695       }
2696       return list.toArray((Object[]) Array.newInstance(componentType, list.size()));
2697     } else {
2698       return o;
2699     }
2700   }
2701 
2702   /**
2703    * Filters hidden elements out of method return values.
2704    */
2705   private static class HideHandler implements InvocationHandler {
2706 
2707     private final Object target;
2708 
HideHandler(Object target)2709     public HideHandler(Object target) {
2710       this.target = target;
2711     }
2712 
invoke(Object proxy, Method method, Object[] args)2713     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
2714       String methodName = method.getName();
2715       if (args != null) {
2716         if (methodName.equals("compareTo") || methodName.equals("equals")
2717             || methodName.equals("overrides") || methodName.equals("subclassOf")) {
2718           args[0] = unwrap(args[0]);
2719         }
2720       }
2721 
2722       if (methodName.equals("getRawCommentText")) {
2723         return filterComment((String) method.invoke(target, args));
2724       }
2725 
2726       // escape "&" in disjunctive types.
2727       if (proxy instanceof Type && methodName.equals("toString")) {
2728         return ((String) method.invoke(target, args)).replace("&", "&amp;");
2729       }
2730 
2731       try {
2732         return filterHiddenAndRemoved(method.invoke(target, args), method.getReturnType());
2733       } catch (InvocationTargetException e) {
2734         throw e.getTargetException();
2735       }
2736     }
2737 
filterComment(String s)2738     private String filterComment(String s) {
2739       if (s == null) {
2740         return null;
2741       }
2742 
2743       s = s.trim();
2744 
2745       // Work around off by one error
2746       while (s.length() >= 5 && s.charAt(s.length() - 5) == '{') {
2747         s += "&nbsp;";
2748       }
2749 
2750       return s;
2751     }
2752 
unwrap(Object proxy)2753     private static Object unwrap(Object proxy) {
2754       if (proxy instanceof Proxy) return ((HideHandler) Proxy.getInvocationHandler(proxy)).target;
2755       return proxy;
2756     }
2757   }
2758 
2759   /**
2760    * Collect the values used by the Dev tools and write them in files packaged with the SDK
2761    *
2762    * @param output the ouput directory for the files.
2763    */
writeSdkValues(String output)2764   private static void writeSdkValues(String output) {
2765     ArrayList<String> activityActions = new ArrayList<String>();
2766     ArrayList<String> broadcastActions = new ArrayList<String>();
2767     ArrayList<String> serviceActions = new ArrayList<String>();
2768     ArrayList<String> categories = new ArrayList<String>();
2769     ArrayList<String> features = new ArrayList<String>();
2770 
2771     ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>();
2772     ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>();
2773     ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>();
2774 
2775     Collection<ClassInfo> classes = Converter.allClasses();
2776 
2777     // The topmost LayoutParams class - android.view.ViewGroup.LayoutParams
2778     ClassInfo topLayoutParams = null;
2779 
2780     // Go through all the fields of all the classes, looking SDK stuff.
2781     for (ClassInfo clazz : classes) {
2782 
2783       // first check constant fields for the SdkConstant annotation.
2784       ArrayList<FieldInfo> fields = clazz.allSelfFields();
2785       for (FieldInfo field : fields) {
2786         Object cValue = field.constantValue();
2787         if (cValue != null) {
2788             ArrayList<AnnotationInstanceInfo> annotations = field.annotations();
2789           if (!annotations.isEmpty()) {
2790             for (AnnotationInstanceInfo annotation : annotations) {
2791               if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) {
2792                 if (!annotation.elementValues().isEmpty()) {
2793                   String type = annotation.elementValues().get(0).valueString();
2794                   if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) {
2795                     activityActions.add(cValue.toString());
2796                   } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) {
2797                     broadcastActions.add(cValue.toString());
2798                   } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) {
2799                     serviceActions.add(cValue.toString());
2800                   } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) {
2801                     categories.add(cValue.toString());
2802                   } else if (SDK_CONSTANT_TYPE_FEATURE.equals(type)) {
2803                     features.add(cValue.toString());
2804                   }
2805                 }
2806                 break;
2807               }
2808             }
2809           }
2810         }
2811       }
2812 
2813       // Now check the class for @Widget or if its in the android.widget package
2814       // (unless the class is hidden or abstract, or non public)
2815       if (clazz.isHiddenOrRemoved() == false && clazz.isPublic() && clazz.isAbstract() == false) {
2816         boolean annotated = false;
2817         ArrayList<AnnotationInstanceInfo> annotations = clazz.annotations();
2818         if (!annotations.isEmpty()) {
2819           for (AnnotationInstanceInfo annotation : annotations) {
2820             if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) {
2821               widgets.add(clazz);
2822               annotated = true;
2823               break;
2824             } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) {
2825               layouts.add(clazz);
2826               annotated = true;
2827               break;
2828             }
2829           }
2830         }
2831 
2832         if (annotated == false) {
2833           if (topLayoutParams == null
2834               && "android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
2835             topLayoutParams = clazz;
2836           }
2837           // let's check if this is inside android.widget or android.view
2838           if (isIncludedPackage(clazz)) {
2839             // now we check what this class inherits either from android.view.ViewGroup
2840             // or android.view.View, or android.view.ViewGroup.LayoutParams
2841             int type = checkInheritance(clazz);
2842             switch (type) {
2843               case TYPE_WIDGET:
2844                 widgets.add(clazz);
2845                 break;
2846               case TYPE_LAYOUT:
2847                 layouts.add(clazz);
2848                 break;
2849               case TYPE_LAYOUT_PARAM:
2850                 layoutParams.add(clazz);
2851                 break;
2852             }
2853           }
2854         }
2855       }
2856     }
2857 
2858     // now write the files, whether or not the list are empty.
2859     // the SDK built requires those files to be present.
2860 
2861     Collections.sort(activityActions);
2862     writeValues(output + "/activity_actions.txt", activityActions);
2863 
2864     Collections.sort(broadcastActions);
2865     writeValues(output + "/broadcast_actions.txt", broadcastActions);
2866 
2867     Collections.sort(serviceActions);
2868     writeValues(output + "/service_actions.txt", serviceActions);
2869 
2870     Collections.sort(categories);
2871     writeValues(output + "/categories.txt", categories);
2872 
2873     Collections.sort(features);
2874     writeValues(output + "/features.txt", features);
2875 
2876     // before writing the list of classes, we do some checks, to make sure the layout params
2877     // are enclosed by a layout class (and not one that has been declared as a widget)
2878     for (int i = 0; i < layoutParams.size();) {
2879       ClassInfo clazz = layoutParams.get(i);
2880       ClassInfo containingClass = clazz.containingClass();
2881       boolean remove = containingClass == null || layouts.indexOf(containingClass) == -1;
2882       // Also ensure that super classes of the layout params are in android.widget or android.view.
2883       while (!remove && (clazz = clazz.superclass()) != null && !clazz.equals(topLayoutParams)) {
2884         remove = !isIncludedPackage(clazz);
2885       }
2886       if (remove) {
2887         layoutParams.remove(i);
2888       } else {
2889         i++;
2890       }
2891     }
2892 
2893     writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams);
2894   }
2895 
2896   /**
2897    * Check if the clazz is in package android.view or android.widget
2898    */
isIncludedPackage(ClassInfo clazz)2899   private static boolean isIncludedPackage(ClassInfo clazz) {
2900     String pckg = clazz.containingPackage().name();
2901     return "android.widget".equals(pckg) || "android.view".equals(pckg);
2902   }
2903 
2904   /**
2905    * Writes a list of values into a text files.
2906    *
2907    * @param pathname the absolute os path of the output file.
2908    * @param values the list of values to write.
2909    */
writeValues(String pathname, ArrayList<String> values)2910   private static void writeValues(String pathname, ArrayList<String> values) {
2911     FileWriter fw = null;
2912     BufferedWriter bw = null;
2913     try {
2914       fw = new FileWriter(pathname, false);
2915       bw = new BufferedWriter(fw);
2916 
2917       for (String value : values) {
2918         bw.append(value).append('\n');
2919       }
2920     } catch (IOException e) {
2921       // pass for now
2922     } finally {
2923       try {
2924         if (bw != null) bw.close();
2925       } catch (IOException e) {
2926         // pass for now
2927       }
2928       try {
2929         if (fw != null) fw.close();
2930       } catch (IOException e) {
2931         // pass for now
2932       }
2933     }
2934   }
2935 
2936   /**
2937    * Writes the widget/layout/layout param classes into a text files.
2938    *
2939    * @param pathname the absolute os path of the output file.
2940    * @param widgets the list of widget classes to write.
2941    * @param layouts the list of layout classes to write.
2942    * @param layoutParams the list of layout param classes to write.
2943    */
writeClasses(String pathname, ArrayList<ClassInfo> widgets, ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams)2944   private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets,
2945       ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) {
2946     FileWriter fw = null;
2947     BufferedWriter bw = null;
2948     try {
2949       fw = new FileWriter(pathname, false);
2950       bw = new BufferedWriter(fw);
2951 
2952       // write the 3 types of classes.
2953       for (ClassInfo clazz : widgets) {
2954         writeClass(bw, clazz, 'W');
2955       }
2956       for (ClassInfo clazz : layoutParams) {
2957         writeClass(bw, clazz, 'P');
2958       }
2959       for (ClassInfo clazz : layouts) {
2960         writeClass(bw, clazz, 'L');
2961       }
2962     } catch (IOException e) {
2963       // pass for now
2964     } finally {
2965       try {
2966         if (bw != null) bw.close();
2967       } catch (IOException e) {
2968         // pass for now
2969       }
2970       try {
2971         if (fw != null) fw.close();
2972       } catch (IOException e) {
2973         // pass for now
2974       }
2975     }
2976   }
2977 
2978   /**
2979    * Writes a class name and its super class names into a {@link BufferedWriter}.
2980    *
2981    * @param writer the BufferedWriter to write into
2982    * @param clazz the class to write
2983    * @param prefix the prefix to put at the beginning of the line.
2984    * @throws IOException
2985    */
writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)2986   private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)
2987       throws IOException {
2988     writer.append(prefix).append(clazz.qualifiedName());
2989     ClassInfo superClass = clazz;
2990     while ((superClass = superClass.superclass()) != null) {
2991       writer.append(' ').append(superClass.qualifiedName());
2992     }
2993     writer.append('\n');
2994   }
2995 
2996   /**
2997    * Checks the inheritance of {@link ClassInfo} objects. This method return
2998    * <ul>
2999    * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li>
3000    * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li>
3001    * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends
3002    * <code>android.view.ViewGroup$LayoutParams</code></li>
3003    * <li>{@link #TYPE_NONE}: in all other cases</li>
3004    * </ul>
3005    *
3006    * @param clazz the {@link ClassInfo} to check.
3007    */
checkInheritance(ClassInfo clazz)3008   private static int checkInheritance(ClassInfo clazz) {
3009     if ("android.view.ViewGroup".equals(clazz.qualifiedName())) {
3010       return TYPE_LAYOUT;
3011     } else if ("android.view.View".equals(clazz.qualifiedName())) {
3012       return TYPE_WIDGET;
3013     } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
3014       return TYPE_LAYOUT_PARAM;
3015     }
3016 
3017     ClassInfo parent = clazz.superclass();
3018     if (parent != null) {
3019       return checkInheritance(parent);
3020     }
3021 
3022     return TYPE_NONE;
3023   }
3024 
3025   /**
3026    * Ensures a trailing '/' at the end of a string.
3027    */
ensureSlash(String path)3028   static String ensureSlash(String path) {
3029     return path.endsWith("/") ? path : path + "/";
3030   }
3031 
3032   /**
3033   * Process sample projects. Generate the TOC for the samples groups and project
3034   * and write it to a cs var, which is then written to files during templating to
3035   * html output. Collect metadata from sample project _index.jd files. Copy html
3036   * and specific source file types to the output directory.
3037   */
writeSamples(boolean offlineMode, ArrayList<SampleCode> sampleCodes, boolean sortNavByGroups)3038   public static void writeSamples(boolean offlineMode, ArrayList<SampleCode> sampleCodes,
3039       boolean sortNavByGroups) {
3040     samplesNavTree = makeHDF();
3041 
3042     // Go through samples processing files. Create a root list for SC nodes,
3043     // pass it to SCs for their NavTree children and append them.
3044     List<SampleCode.Node> samplesList = new ArrayList<SampleCode.Node>();
3045     List<SampleCode.Node> sampleGroupsRootNodes = null;
3046     for (SampleCode sc : sampleCodes) {
3047       samplesList.add(sc.setSamplesTOC(offlineMode));
3048      }
3049     if (sortNavByGroups) {
3050       sampleGroupsRootNodes = new ArrayList<SampleCode.Node>();
3051       for (SampleCode gsc : sampleCodeGroups) {
3052         String link =  ClearPage.toroot + "samples/" + gsc.mTitle.replaceAll(" ", "").trim().toLowerCase() + ".html";
3053         sampleGroupsRootNodes.add(new SampleCode.Node.Builder().setLabel(gsc.mTitle).setLink(link).setType("groupholder").build());
3054       }
3055     }
3056     // Pass full samplesList to SC to render the samples TOC to sampleNavTree hdf
3057     if (!offlineMode) {
3058       SampleCode.writeSamplesNavTree(samplesList, sampleGroupsRootNodes);
3059     }
3060     // Iterate the samplecode projects writing the files to out
3061     for (SampleCode sc : sampleCodes) {
3062       sc.writeSamplesFiles(offlineMode);
3063     }
3064   }
3065 
3066   /**
3067   * Given an initial samples directory root, walk through the directory collecting
3068   * sample code project roots and adding them to an array of SampleCodes.
3069   * @param rootDir Root directory holding all browseable sample code projects,
3070   *        defined in frameworks/base/Android.mk as "-sampleDir path".
3071   */
getSampleProjects(File rootDir)3072   public static void getSampleProjects(File rootDir) {
3073     for (File f : rootDir.listFiles()) {
3074       String name = f.getName();
3075       if (f.isDirectory()) {
3076         if (isValidSampleProjectRoot(f)) {
3077           sampleCodes.add(new SampleCode(f.getAbsolutePath(), "samples/" + name, name));
3078         } else {
3079           getSampleProjects(f);
3080         }
3081       }
3082     }
3083   }
3084 
3085   /**
3086   * Test whether a given directory is the root directory for a sample code project.
3087   * Root directories must contain a valid _index.jd file and a src/ directory
3088   * or a module directory that contains a src/ directory.
3089   */
isValidSampleProjectRoot(File dir)3090   public static boolean isValidSampleProjectRoot(File dir) {
3091     File indexJd = new File(dir, "_index.jd");
3092     if (!indexJd.exists()) {
3093       return false;
3094     }
3095     File srcDir = new File(dir, "src");
3096     if (srcDir.exists()) {
3097       return true;
3098     } else {
3099       // Look for a src/ directory one level below the root directory, so
3100       // modules are supported.
3101       for (File childDir : dir.listFiles()) {
3102         if (childDir.isDirectory()) {
3103           srcDir = new File(childDir, "src");
3104           if (srcDir.exists()) {
3105             return true;
3106           }
3107         }
3108       }
3109       return false;
3110     }
3111   }
3112 
getDocumentationStringForAnnotation(String annotationName)3113   public static String getDocumentationStringForAnnotation(String annotationName) {
3114     if (!documentAnnotations) return null;
3115     if (annotationDocumentationMap == null) {
3116       annotationDocumentationMap = new HashMap<String, String>();
3117       // parse the file for map
3118       try {
3119         BufferedReader in = new BufferedReader(
3120             new FileReader(documentAnnotationsPath));
3121         try {
3122           String line = in.readLine();
3123           String[] split;
3124           while (line != null) {
3125             split = line.split(":");
3126             annotationDocumentationMap.put(split[0], split[1]);
3127             line = in.readLine();
3128           }
3129         } finally {
3130           in.close();
3131         }
3132       } catch (IOException e) {
3133         System.err.println("Unable to open annotations documentation file for reading: "
3134             + documentAnnotationsPath);
3135       }
3136     }
3137     return annotationDocumentationMap.get(annotationName);
3138   }
3139 
writeCompatConfig()3140   public static void writeCompatConfig() {
3141     if (compatConfig == null) {
3142       return;
3143     }
3144     CompatInfo config = CompatInfo.readCompatConfig(compatConfig);
3145     Data data = makeHDF();
3146     config.makeHDF(data);
3147     setPageTitle(data, "Compatibility changes");
3148     // TODO - should we write the output to some other path?
3149     String outfile = "compatchanges.html";
3150     ClearPage.write(data, "compatchanges.cs", outfile);
3151   }
3152 
3153 }
3154