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