1*feeed43cSAndroid Build Coastguard Worker /* 2*feeed43cSAndroid Build Coastguard Worker * Copyright (C) 2017 Google Inc. 3*feeed43cSAndroid Build Coastguard Worker * 4*feeed43cSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*feeed43cSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*feeed43cSAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*feeed43cSAndroid Build Coastguard Worker * 8*feeed43cSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*feeed43cSAndroid Build Coastguard Worker * 10*feeed43cSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*feeed43cSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*feeed43cSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*feeed43cSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*feeed43cSAndroid Build Coastguard Worker * limitations under the License. 15*feeed43cSAndroid Build Coastguard Worker */ 16*feeed43cSAndroid Build Coastguard Worker 17*feeed43cSAndroid Build Coastguard Worker package com.google.doclava; 18*feeed43cSAndroid Build Coastguard Worker 19*feeed43cSAndroid Build Coastguard Worker import java.util.ArrayList; 20*feeed43cSAndroid Build Coastguard Worker import java.util.HashMap; 21*feeed43cSAndroid Build Coastguard Worker import java.util.List; 22*feeed43cSAndroid Build Coastguard Worker import java.util.Map; 23*feeed43cSAndroid Build Coastguard Worker import java.util.regex.Pattern; 24*feeed43cSAndroid Build Coastguard Worker 25*feeed43cSAndroid Build Coastguard Worker public class AndroidAuxSource implements AuxSource { 26*feeed43cSAndroid Build Coastguard Worker private static final int TYPE_CLASS = 0; 27*feeed43cSAndroid Build Coastguard Worker private static final int TYPE_FIELD = 1; 28*feeed43cSAndroid Build Coastguard Worker private static final int TYPE_METHOD = 2; 29*feeed43cSAndroid Build Coastguard Worker private static final int TYPE_PARAM = 3; 30*feeed43cSAndroid Build Coastguard Worker private static final int TYPE_RETURN = 4; 31*feeed43cSAndroid Build Coastguard Worker 32*feeed43cSAndroid Build Coastguard Worker @Override classAuxTags(ClassInfo clazz)33*feeed43cSAndroid Build Coastguard Worker public TagInfo[] classAuxTags(ClassInfo clazz) { 34*feeed43cSAndroid Build Coastguard Worker if (hasSuppress(clazz.annotations())) return TagInfo.EMPTY_ARRAY; 35*feeed43cSAndroid Build Coastguard Worker ArrayList<TagInfo> tags = new ArrayList<>(); 36*feeed43cSAndroid Build Coastguard Worker for (AnnotationInstanceInfo annotation : clazz.annotations()) { 37*feeed43cSAndroid Build Coastguard Worker // Document system services 38*feeed43cSAndroid Build Coastguard Worker if (annotation.type().qualifiedNameMatches("android", "annotation.SystemService")) { 39*feeed43cSAndroid Build Coastguard Worker ArrayList<TagInfo> valueTags = new ArrayList<>(); 40*feeed43cSAndroid Build Coastguard Worker valueTags 41*feeed43cSAndroid Build Coastguard Worker .add(new ParsedTagInfo("", "", 42*feeed43cSAndroid Build Coastguard Worker "{@link android.content.Context#getSystemService(Class)" 43*feeed43cSAndroid Build Coastguard Worker + " Context.getSystemService(Class)}", 44*feeed43cSAndroid Build Coastguard Worker null, SourcePositionInfo.UNKNOWN)); 45*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 46*feeed43cSAndroid Build Coastguard Worker "{@code " + clazz.name() + ".class}", null, 47*feeed43cSAndroid Build Coastguard Worker SourcePositionInfo.UNKNOWN)); 48*feeed43cSAndroid Build Coastguard Worker 49*feeed43cSAndroid Build Coastguard Worker ClassInfo contextClass = annotation.type().findClass("android.content.Context"); 50*feeed43cSAndroid Build Coastguard Worker for (AnnotationValueInfo val : annotation.elementValues()) { 51*feeed43cSAndroid Build Coastguard Worker switch (val.element().name()) { 52*feeed43cSAndroid Build Coastguard Worker case "value": 53*feeed43cSAndroid Build Coastguard Worker final String expected = String.valueOf(val.value()); 54*feeed43cSAndroid Build Coastguard Worker for (FieldInfo field : contextClass.fields()) { 55*feeed43cSAndroid Build Coastguard Worker if (field.isHiddenOrRemoved()) continue; 56*feeed43cSAndroid Build Coastguard Worker if (String.valueOf(field.constantValue()).equals(expected)) { 57*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 58*feeed43cSAndroid Build Coastguard Worker "{@link android.content.Context#getSystemService(String)" 59*feeed43cSAndroid Build Coastguard Worker + " Context.getSystemService(String)}", 60*feeed43cSAndroid Build Coastguard Worker null, SourcePositionInfo.UNKNOWN)); 61*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 62*feeed43cSAndroid Build Coastguard Worker "{@link android.content.Context#" + field.name() 63*feeed43cSAndroid Build Coastguard Worker + " Context." + field.name() + "}", 64*feeed43cSAndroid Build Coastguard Worker null, SourcePositionInfo.UNKNOWN)); 65*feeed43cSAndroid Build Coastguard Worker } 66*feeed43cSAndroid Build Coastguard Worker } 67*feeed43cSAndroid Build Coastguard Worker break; 68*feeed43cSAndroid Build Coastguard Worker } 69*feeed43cSAndroid Build Coastguard Worker } 70*feeed43cSAndroid Build Coastguard Worker 71*feeed43cSAndroid Build Coastguard Worker Map<String, String> args = new HashMap<>(); 72*feeed43cSAndroid Build Coastguard Worker tags.add(new AuxTagInfo("@service", "@service", SourcePositionInfo.UNKNOWN, args, 73*feeed43cSAndroid Build Coastguard Worker valueTags.toArray(TagInfo.getArray(valueTags.size())))); 74*feeed43cSAndroid Build Coastguard Worker } 75*feeed43cSAndroid Build Coastguard Worker } 76*feeed43cSAndroid Build Coastguard Worker auxTags(TYPE_CLASS, clazz.annotations(), toString(clazz.inlineTags()), tags); 77*feeed43cSAndroid Build Coastguard Worker return tags.toArray(TagInfo.getArray(tags.size())); 78*feeed43cSAndroid Build Coastguard Worker } 79*feeed43cSAndroid Build Coastguard Worker 80*feeed43cSAndroid Build Coastguard Worker @Override fieldAuxTags(FieldInfo field)81*feeed43cSAndroid Build Coastguard Worker public TagInfo[] fieldAuxTags(FieldInfo field) { 82*feeed43cSAndroid Build Coastguard Worker if (hasSuppress(field)) return TagInfo.EMPTY_ARRAY; 83*feeed43cSAndroid Build Coastguard Worker return auxTags(TYPE_FIELD, field.annotations(), toString(field.inlineTags())); 84*feeed43cSAndroid Build Coastguard Worker } 85*feeed43cSAndroid Build Coastguard Worker 86*feeed43cSAndroid Build Coastguard Worker @Override methodAuxTags(MethodInfo method)87*feeed43cSAndroid Build Coastguard Worker public TagInfo[] methodAuxTags(MethodInfo method) { 88*feeed43cSAndroid Build Coastguard Worker if (hasSuppress(method)) return TagInfo.EMPTY_ARRAY; 89*feeed43cSAndroid Build Coastguard Worker return auxTags(TYPE_METHOD, method.annotations(), toString(method.inlineTags().tags())); 90*feeed43cSAndroid Build Coastguard Worker } 91*feeed43cSAndroid Build Coastguard Worker 92*feeed43cSAndroid Build Coastguard Worker @Override paramAuxTags(MethodInfo method, ParameterInfo param, String comment)93*feeed43cSAndroid Build Coastguard Worker public TagInfo[] paramAuxTags(MethodInfo method, ParameterInfo param, String comment) { 94*feeed43cSAndroid Build Coastguard Worker if (hasSuppress(method)) return TagInfo.EMPTY_ARRAY; 95*feeed43cSAndroid Build Coastguard Worker if (hasSuppress(param.annotations())) return TagInfo.EMPTY_ARRAY; 96*feeed43cSAndroid Build Coastguard Worker return auxTags(TYPE_PARAM, param.annotations(), new String[] { comment }); 97*feeed43cSAndroid Build Coastguard Worker } 98*feeed43cSAndroid Build Coastguard Worker 99*feeed43cSAndroid Build Coastguard Worker @Override returnAuxTags(MethodInfo method)100*feeed43cSAndroid Build Coastguard Worker public TagInfo[] returnAuxTags(MethodInfo method) { 101*feeed43cSAndroid Build Coastguard Worker if (hasSuppress(method)) return TagInfo.EMPTY_ARRAY; 102*feeed43cSAndroid Build Coastguard Worker return auxTags(TYPE_RETURN, method.annotations(), toString(method.returnTags().tags())); 103*feeed43cSAndroid Build Coastguard Worker } 104*feeed43cSAndroid Build Coastguard Worker auxTags(int type, List<AnnotationInstanceInfo> annotations, String[] comment)105*feeed43cSAndroid Build Coastguard Worker private static TagInfo[] auxTags(int type, List<AnnotationInstanceInfo> annotations, 106*feeed43cSAndroid Build Coastguard Worker String[] comment) { 107*feeed43cSAndroid Build Coastguard Worker ArrayList<TagInfo> tags = new ArrayList<>(); 108*feeed43cSAndroid Build Coastguard Worker auxTags(type, annotations, comment, tags); 109*feeed43cSAndroid Build Coastguard Worker return tags.toArray(TagInfo.getArray(tags.size())); 110*feeed43cSAndroid Build Coastguard Worker } 111*feeed43cSAndroid Build Coastguard Worker auxTags(int type, List<AnnotationInstanceInfo> annotations, String[] comment, ArrayList<TagInfo> tags)112*feeed43cSAndroid Build Coastguard Worker private static void auxTags(int type, List<AnnotationInstanceInfo> annotations, 113*feeed43cSAndroid Build Coastguard Worker String[] comment, ArrayList<TagInfo> tags) { 114*feeed43cSAndroid Build Coastguard Worker for (AnnotationInstanceInfo annotation : annotations) { 115*feeed43cSAndroid Build Coastguard Worker // Ignore null-related annotations when docs already mention 116*feeed43cSAndroid Build Coastguard Worker if (annotation.type().qualifiedNameMatches("android", "annotation.NonNull") 117*feeed43cSAndroid Build Coastguard Worker || annotation.type().qualifiedNameMatches("android", "annotation.Nullable")) { 118*feeed43cSAndroid Build Coastguard Worker boolean mentionsNull = false; 119*feeed43cSAndroid Build Coastguard Worker for (String c : comment) { 120*feeed43cSAndroid Build Coastguard Worker mentionsNull |= Pattern.compile("\\bnull\\b").matcher(c).find(); 121*feeed43cSAndroid Build Coastguard Worker } 122*feeed43cSAndroid Build Coastguard Worker if (mentionsNull) { 123*feeed43cSAndroid Build Coastguard Worker continue; 124*feeed43cSAndroid Build Coastguard Worker } 125*feeed43cSAndroid Build Coastguard Worker } 126*feeed43cSAndroid Build Coastguard Worker 127*feeed43cSAndroid Build Coastguard Worker // Blindly include docs requested by annotations 128*feeed43cSAndroid Build Coastguard Worker ParsedTagInfo[] docTags = ParsedTagInfo.EMPTY_ARRAY; 129*feeed43cSAndroid Build Coastguard Worker switch (type) { 130*feeed43cSAndroid Build Coastguard Worker case TYPE_METHOD: 131*feeed43cSAndroid Build Coastguard Worker case TYPE_FIELD: 132*feeed43cSAndroid Build Coastguard Worker case TYPE_CLASS: 133*feeed43cSAndroid Build Coastguard Worker docTags = annotation.type().comment().memberDocTags(); 134*feeed43cSAndroid Build Coastguard Worker break; 135*feeed43cSAndroid Build Coastguard Worker case TYPE_PARAM: 136*feeed43cSAndroid Build Coastguard Worker docTags = annotation.type().comment().paramDocTags(); 137*feeed43cSAndroid Build Coastguard Worker break; 138*feeed43cSAndroid Build Coastguard Worker case TYPE_RETURN: 139*feeed43cSAndroid Build Coastguard Worker docTags = annotation.type().comment().returnDocTags(); 140*feeed43cSAndroid Build Coastguard Worker break; 141*feeed43cSAndroid Build Coastguard Worker } 142*feeed43cSAndroid Build Coastguard Worker for (ParsedTagInfo docTag : docTags) { 143*feeed43cSAndroid Build Coastguard Worker tags.add(docTag); 144*feeed43cSAndroid Build Coastguard Worker } 145*feeed43cSAndroid Build Coastguard Worker 146*feeed43cSAndroid Build Coastguard Worker // Document required permissions 147*feeed43cSAndroid Build Coastguard Worker if ((type == TYPE_CLASS || type == TYPE_METHOD || type == TYPE_FIELD) 148*feeed43cSAndroid Build Coastguard Worker && annotation.type().qualifiedNameMatches("android", "annotation.RequiresPermission")) { 149*feeed43cSAndroid Build Coastguard Worker ArrayList<AnnotationValueInfo> values = new ArrayList<>(); 150*feeed43cSAndroid Build Coastguard Worker boolean any = false; 151*feeed43cSAndroid Build Coastguard Worker for (AnnotationValueInfo val : annotation.elementValues()) { 152*feeed43cSAndroid Build Coastguard Worker switch (val.element().name()) { 153*feeed43cSAndroid Build Coastguard Worker case "value": 154*feeed43cSAndroid Build Coastguard Worker values.add(val); 155*feeed43cSAndroid Build Coastguard Worker break; 156*feeed43cSAndroid Build Coastguard Worker case "allOf": 157*feeed43cSAndroid Build Coastguard Worker values = (ArrayList<AnnotationValueInfo>) val.value(); 158*feeed43cSAndroid Build Coastguard Worker break; 159*feeed43cSAndroid Build Coastguard Worker case "anyOf": 160*feeed43cSAndroid Build Coastguard Worker any = true; 161*feeed43cSAndroid Build Coastguard Worker values = (ArrayList<AnnotationValueInfo>) val.value(); 162*feeed43cSAndroid Build Coastguard Worker break; 163*feeed43cSAndroid Build Coastguard Worker } 164*feeed43cSAndroid Build Coastguard Worker } 165*feeed43cSAndroid Build Coastguard Worker if (values.isEmpty()) continue; 166*feeed43cSAndroid Build Coastguard Worker 167*feeed43cSAndroid Build Coastguard Worker ClassInfo permClass = annotation.type().findClass("android.Manifest.permission"); 168*feeed43cSAndroid Build Coastguard Worker ArrayList<TagInfo> valueTags = new ArrayList<>(); 169*feeed43cSAndroid Build Coastguard Worker for (AnnotationValueInfo value : values) { 170*feeed43cSAndroid Build Coastguard Worker final String expected = String.valueOf(value.value()); 171*feeed43cSAndroid Build Coastguard Worker for (FieldInfo field : permClass.fields()) { 172*feeed43cSAndroid Build Coastguard Worker if (field.isHiddenOrRemoved()) continue; 173*feeed43cSAndroid Build Coastguard Worker if (String.valueOf(field.constantValue()).equals(expected)) { 174*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 175*feeed43cSAndroid Build Coastguard Worker "{@link " + permClass.qualifiedName() + "#" + field.name() + "}", null, 176*feeed43cSAndroid Build Coastguard Worker SourcePositionInfo.UNKNOWN)); 177*feeed43cSAndroid Build Coastguard Worker } 178*feeed43cSAndroid Build Coastguard Worker } 179*feeed43cSAndroid Build Coastguard Worker } 180*feeed43cSAndroid Build Coastguard Worker 181*feeed43cSAndroid Build Coastguard Worker Map<String, String> args = new HashMap<>(); 182*feeed43cSAndroid Build Coastguard Worker if (any) args.put("any", "true"); 183*feeed43cSAndroid Build Coastguard Worker tags.add(new AuxTagInfo("@permission", "@permission", SourcePositionInfo.UNKNOWN, args, 184*feeed43cSAndroid Build Coastguard Worker valueTags.toArray(TagInfo.getArray(valueTags.size())))); 185*feeed43cSAndroid Build Coastguard Worker } 186*feeed43cSAndroid Build Coastguard Worker 187*feeed43cSAndroid Build Coastguard Worker // Document required features 188*feeed43cSAndroid Build Coastguard Worker if ((type == TYPE_CLASS || type == TYPE_METHOD || type == TYPE_FIELD) 189*feeed43cSAndroid Build Coastguard Worker && annotation.type().qualifiedNameMatches("android", "annotation.RequiresFeature")) { 190*feeed43cSAndroid Build Coastguard Worker AnnotationValueInfo value = null; 191*feeed43cSAndroid Build Coastguard Worker for (AnnotationValueInfo val : annotation.elementValues()) { 192*feeed43cSAndroid Build Coastguard Worker switch (val.element().name()) { 193*feeed43cSAndroid Build Coastguard Worker case "value": 194*feeed43cSAndroid Build Coastguard Worker value = val; 195*feeed43cSAndroid Build Coastguard Worker break; 196*feeed43cSAndroid Build Coastguard Worker } 197*feeed43cSAndroid Build Coastguard Worker } 198*feeed43cSAndroid Build Coastguard Worker if (value == null) continue; 199*feeed43cSAndroid Build Coastguard Worker 200*feeed43cSAndroid Build Coastguard Worker ClassInfo pmClass = annotation.type().findClass("android.content.pm.PackageManager"); 201*feeed43cSAndroid Build Coastguard Worker ArrayList<TagInfo> valueTags = new ArrayList<>(); 202*feeed43cSAndroid Build Coastguard Worker final String expected = String.valueOf(value.value()); 203*feeed43cSAndroid Build Coastguard Worker for (FieldInfo field : pmClass.fields()) { 204*feeed43cSAndroid Build Coastguard Worker if (field.isHiddenOrRemoved()) continue; 205*feeed43cSAndroid Build Coastguard Worker if (String.valueOf(field.constantValue()).equals(expected)) { 206*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 207*feeed43cSAndroid Build Coastguard Worker "{@link " + pmClass.qualifiedName() + "#" + field.name() + "}", null, 208*feeed43cSAndroid Build Coastguard Worker SourcePositionInfo.UNKNOWN)); 209*feeed43cSAndroid Build Coastguard Worker } 210*feeed43cSAndroid Build Coastguard Worker } 211*feeed43cSAndroid Build Coastguard Worker 212*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 213*feeed43cSAndroid Build Coastguard Worker "{@link android.content.pm.PackageManager#hasSystemFeature(String)" 214*feeed43cSAndroid Build Coastguard Worker + " PackageManager.hasSystemFeature(String)}", 215*feeed43cSAndroid Build Coastguard Worker null, SourcePositionInfo.UNKNOWN)); 216*feeed43cSAndroid Build Coastguard Worker 217*feeed43cSAndroid Build Coastguard Worker Map<String, String> args = new HashMap<>(); 218*feeed43cSAndroid Build Coastguard Worker tags.add(new AuxTagInfo("@feature", "@feature", SourcePositionInfo.UNKNOWN, args, 219*feeed43cSAndroid Build Coastguard Worker valueTags.toArray(TagInfo.getArray(valueTags.size())))); 220*feeed43cSAndroid Build Coastguard Worker } 221*feeed43cSAndroid Build Coastguard Worker 222*feeed43cSAndroid Build Coastguard Worker // Document provider columns 223*feeed43cSAndroid Build Coastguard Worker if ((type == TYPE_FIELD) && annotation.type().qualifiedNameMatches("android", "Column")) { 224*feeed43cSAndroid Build Coastguard Worker String value = null; 225*feeed43cSAndroid Build Coastguard Worker boolean readOnly = false; 226*feeed43cSAndroid Build Coastguard Worker for (AnnotationValueInfo val : annotation.elementValues()) { 227*feeed43cSAndroid Build Coastguard Worker switch (val.element().name()) { 228*feeed43cSAndroid Build Coastguard Worker case "value": 229*feeed43cSAndroid Build Coastguard Worker value = String.valueOf(val.value()); 230*feeed43cSAndroid Build Coastguard Worker break; 231*feeed43cSAndroid Build Coastguard Worker case "readOnly": 232*feeed43cSAndroid Build Coastguard Worker readOnly = Boolean.parseBoolean(String.valueOf(val.value())); 233*feeed43cSAndroid Build Coastguard Worker break; 234*feeed43cSAndroid Build Coastguard Worker } 235*feeed43cSAndroid Build Coastguard Worker } 236*feeed43cSAndroid Build Coastguard Worker 237*feeed43cSAndroid Build Coastguard Worker ArrayList<TagInfo> valueTags = new ArrayList<>(); 238*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 239*feeed43cSAndroid Build Coastguard Worker "{@link android.content.ContentProvider}", null, SourcePositionInfo.UNKNOWN)); 240*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 241*feeed43cSAndroid Build Coastguard Worker "{@link android.content.ContentValues}", null, SourcePositionInfo.UNKNOWN)); 242*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 243*feeed43cSAndroid Build Coastguard Worker "{@link android.database.Cursor}", null, SourcePositionInfo.UNKNOWN)); 244*feeed43cSAndroid Build Coastguard Worker 245*feeed43cSAndroid Build Coastguard Worker ClassInfo cursorClass = annotation.type().findClass("android.database.Cursor"); 246*feeed43cSAndroid Build Coastguard Worker for (FieldInfo field : cursorClass.fields()) { 247*feeed43cSAndroid Build Coastguard Worker if (field.isHiddenOrRemoved()) continue; 248*feeed43cSAndroid Build Coastguard Worker if (String.valueOf(field.constantValue()).equals(value)) { 249*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 250*feeed43cSAndroid Build Coastguard Worker "{@link android.database.Cursor#" + field.name() + "}", 251*feeed43cSAndroid Build Coastguard Worker null, SourcePositionInfo.UNKNOWN)); 252*feeed43cSAndroid Build Coastguard Worker } 253*feeed43cSAndroid Build Coastguard Worker } 254*feeed43cSAndroid Build Coastguard Worker if (valueTags.size() < 4) continue; 255*feeed43cSAndroid Build Coastguard Worker 256*feeed43cSAndroid Build Coastguard Worker Map<String, String> args = new HashMap<>(); 257*feeed43cSAndroid Build Coastguard Worker if (readOnly) args.put("readOnly", "true"); 258*feeed43cSAndroid Build Coastguard Worker tags.add(new AuxTagInfo("@column", "@column", SourcePositionInfo.UNKNOWN, args, 259*feeed43cSAndroid Build Coastguard Worker valueTags.toArray(TagInfo.getArray(valueTags.size())))); 260*feeed43cSAndroid Build Coastguard Worker } 261*feeed43cSAndroid Build Coastguard Worker 262*feeed43cSAndroid Build Coastguard Worker // The remaining annotations below always appear on return docs, and 263*feeed43cSAndroid Build Coastguard Worker // should not be included in the method body 264*feeed43cSAndroid Build Coastguard Worker if (type == TYPE_METHOD) continue; 265*feeed43cSAndroid Build Coastguard Worker 266*feeed43cSAndroid Build Coastguard Worker // Document value ranges 267*feeed43cSAndroid Build Coastguard Worker if (annotation.type().qualifiedNameMatches("android", "annotation.IntRange") 268*feeed43cSAndroid Build Coastguard Worker || annotation.type().qualifiedNameMatches("android", "annotation.FloatRange")) { 269*feeed43cSAndroid Build Coastguard Worker String from = null; 270*feeed43cSAndroid Build Coastguard Worker String to = null; 271*feeed43cSAndroid Build Coastguard Worker for (AnnotationValueInfo val : annotation.elementValues()) { 272*feeed43cSAndroid Build Coastguard Worker switch (val.element().name()) { 273*feeed43cSAndroid Build Coastguard Worker case "from": from = String.valueOf(val.value()); break; 274*feeed43cSAndroid Build Coastguard Worker case "to": to = String.valueOf(val.value()); break; 275*feeed43cSAndroid Build Coastguard Worker } 276*feeed43cSAndroid Build Coastguard Worker } 277*feeed43cSAndroid Build Coastguard Worker if (from != null || to != null) { 278*feeed43cSAndroid Build Coastguard Worker Map<String, String> args = new HashMap<>(); 279*feeed43cSAndroid Build Coastguard Worker if (from != null) args.put("from", from); 280*feeed43cSAndroid Build Coastguard Worker if (to != null) args.put("to", to); 281*feeed43cSAndroid Build Coastguard Worker tags.add(new AuxTagInfo("@range", "@range", SourcePositionInfo.UNKNOWN, args, 282*feeed43cSAndroid Build Coastguard Worker TagInfo.EMPTY_ARRAY)); 283*feeed43cSAndroid Build Coastguard Worker } 284*feeed43cSAndroid Build Coastguard Worker } 285*feeed43cSAndroid Build Coastguard Worker 286*feeed43cSAndroid Build Coastguard Worker // Document integer values 287*feeed43cSAndroid Build Coastguard Worker for (AnnotationInstanceInfo inner : annotation.type().annotations()) { 288*feeed43cSAndroid Build Coastguard Worker boolean intDef = inner.type().qualifiedNameMatches("android", "annotation.IntDef"); 289*feeed43cSAndroid Build Coastguard Worker boolean stringDef = inner.type().qualifiedNameMatches("android", "annotation.StringDef"); 290*feeed43cSAndroid Build Coastguard Worker if (intDef || stringDef) { 291*feeed43cSAndroid Build Coastguard Worker ArrayList<AnnotationValueInfo> prefixes = null; 292*feeed43cSAndroid Build Coastguard Worker ArrayList<AnnotationValueInfo> suffixes = null; 293*feeed43cSAndroid Build Coastguard Worker ArrayList<AnnotationValueInfo> values = null; 294*feeed43cSAndroid Build Coastguard Worker final String kind = intDef ? "@intDef" : "@stringDef"; 295*feeed43cSAndroid Build Coastguard Worker boolean flag = false; 296*feeed43cSAndroid Build Coastguard Worker 297*feeed43cSAndroid Build Coastguard Worker for (AnnotationValueInfo val : inner.elementValues()) { 298*feeed43cSAndroid Build Coastguard Worker switch (val.element().name()) { 299*feeed43cSAndroid Build Coastguard Worker case "prefix": prefixes = (ArrayList<AnnotationValueInfo>) val.value(); break; 300*feeed43cSAndroid Build Coastguard Worker case "suffix": suffixes = (ArrayList<AnnotationValueInfo>) val.value(); break; 301*feeed43cSAndroid Build Coastguard Worker case "value": values = (ArrayList<AnnotationValueInfo>) val.value(); break; 302*feeed43cSAndroid Build Coastguard Worker case "flag": flag = Boolean.parseBoolean(String.valueOf(val.value())); break; 303*feeed43cSAndroid Build Coastguard Worker } 304*feeed43cSAndroid Build Coastguard Worker } 305*feeed43cSAndroid Build Coastguard Worker 306*feeed43cSAndroid Build Coastguard Worker // Sadly we can only generate docs when told about a prefix/suffix 307*feeed43cSAndroid Build Coastguard Worker if (prefixes == null) prefixes = new ArrayList<>(); 308*feeed43cSAndroid Build Coastguard Worker if (suffixes == null) suffixes = new ArrayList<>(); 309*feeed43cSAndroid Build Coastguard Worker if (prefixes.isEmpty() && suffixes.isEmpty()) continue; 310*feeed43cSAndroid Build Coastguard Worker 311*feeed43cSAndroid Build Coastguard Worker final ClassInfo clazz = annotation.type().containingClass(); 312*feeed43cSAndroid Build Coastguard Worker final HashMap<String, FieldInfo> candidates = new HashMap<>(); 313*feeed43cSAndroid Build Coastguard Worker for (FieldInfo field : clazz.fields()) { 314*feeed43cSAndroid Build Coastguard Worker if (field.isHiddenOrRemoved()) continue; 315*feeed43cSAndroid Build Coastguard Worker for (AnnotationValueInfo prefix : prefixes) { 316*feeed43cSAndroid Build Coastguard Worker if (field.name().startsWith(String.valueOf(prefix.value()))) { 317*feeed43cSAndroid Build Coastguard Worker candidates.put(String.valueOf(field.constantValue()), field); 318*feeed43cSAndroid Build Coastguard Worker } 319*feeed43cSAndroid Build Coastguard Worker } 320*feeed43cSAndroid Build Coastguard Worker for (AnnotationValueInfo suffix : suffixes) { 321*feeed43cSAndroid Build Coastguard Worker if (field.name().endsWith(String.valueOf(suffix.value()))) { 322*feeed43cSAndroid Build Coastguard Worker candidates.put(String.valueOf(field.constantValue()), field); 323*feeed43cSAndroid Build Coastguard Worker } 324*feeed43cSAndroid Build Coastguard Worker } 325*feeed43cSAndroid Build Coastguard Worker } 326*feeed43cSAndroid Build Coastguard Worker 327*feeed43cSAndroid Build Coastguard Worker ArrayList<TagInfo> valueTags = new ArrayList<>(); 328*feeed43cSAndroid Build Coastguard Worker for (AnnotationValueInfo value : values) { 329*feeed43cSAndroid Build Coastguard Worker final String expected = String.valueOf(value.value()); 330*feeed43cSAndroid Build Coastguard Worker final FieldInfo field = candidates.remove(expected); 331*feeed43cSAndroid Build Coastguard Worker if (field != null) { 332*feeed43cSAndroid Build Coastguard Worker valueTags.add(new ParsedTagInfo("", "", 333*feeed43cSAndroid Build Coastguard Worker "{@link " + clazz.qualifiedName() + "#" + field.name() + "}", null, 334*feeed43cSAndroid Build Coastguard Worker SourcePositionInfo.UNKNOWN)); 335*feeed43cSAndroid Build Coastguard Worker } 336*feeed43cSAndroid Build Coastguard Worker } 337*feeed43cSAndroid Build Coastguard Worker 338*feeed43cSAndroid Build Coastguard Worker if (!valueTags.isEmpty()) { 339*feeed43cSAndroid Build Coastguard Worker Map<String, String> args = new HashMap<>(); 340*feeed43cSAndroid Build Coastguard Worker if (flag) args.put("flag", "true"); 341*feeed43cSAndroid Build Coastguard Worker tags.add(new AuxTagInfo(kind, kind, SourcePositionInfo.UNKNOWN, args, 342*feeed43cSAndroid Build Coastguard Worker valueTags.toArray(TagInfo.getArray(valueTags.size())))); 343*feeed43cSAndroid Build Coastguard Worker } 344*feeed43cSAndroid Build Coastguard Worker } 345*feeed43cSAndroid Build Coastguard Worker } 346*feeed43cSAndroid Build Coastguard Worker } 347*feeed43cSAndroid Build Coastguard Worker } 348*feeed43cSAndroid Build Coastguard Worker toString(TagInfo[] tags)349*feeed43cSAndroid Build Coastguard Worker private static String[] toString(TagInfo[] tags) { 350*feeed43cSAndroid Build Coastguard Worker final String[] res = new String[tags.length]; 351*feeed43cSAndroid Build Coastguard Worker for (int i = 0; i < res.length; i++) { 352*feeed43cSAndroid Build Coastguard Worker res[i] = tags[i].text(); 353*feeed43cSAndroid Build Coastguard Worker } 354*feeed43cSAndroid Build Coastguard Worker return res; 355*feeed43cSAndroid Build Coastguard Worker } 356*feeed43cSAndroid Build Coastguard Worker hasSuppress(MemberInfo member)357*feeed43cSAndroid Build Coastguard Worker private static boolean hasSuppress(MemberInfo member) { 358*feeed43cSAndroid Build Coastguard Worker return hasSuppress(member.annotations()) 359*feeed43cSAndroid Build Coastguard Worker || hasSuppress(member.containingClass().annotations()); 360*feeed43cSAndroid Build Coastguard Worker } 361*feeed43cSAndroid Build Coastguard Worker hasSuppress(List<AnnotationInstanceInfo> annotations)362*feeed43cSAndroid Build Coastguard Worker private static boolean hasSuppress(List<AnnotationInstanceInfo> annotations) { 363*feeed43cSAndroid Build Coastguard Worker for (AnnotationInstanceInfo annotation : annotations) { 364*feeed43cSAndroid Build Coastguard Worker if (annotation.type().qualifiedNameMatches("android", "annotation.SuppressAutoDoc")) { 365*feeed43cSAndroid Build Coastguard Worker return true; 366*feeed43cSAndroid Build Coastguard Worker } 367*feeed43cSAndroid Build Coastguard Worker } 368*feeed43cSAndroid Build Coastguard Worker return false; 369*feeed43cSAndroid Build Coastguard Worker } 370*feeed43cSAndroid Build Coastguard Worker } 371