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  * Originally from Doclava project at
18  * https://android.googlesource.com/platform/external/doclava/+/master/src/com/google/doclava/Doclava.java
19  */
20 
21 package org.conscrypt.doclet;
22 
23 import com.sun.javadoc.*;
24 import com.sun.tools.doclets.standard.Standard;
25 import com.sun.tools.javadoc.Main;
26 import java.io.FileNotFoundException;
27 import java.lang.reflect.Array;
28 import java.lang.reflect.InvocationHandler;
29 import java.lang.reflect.InvocationTargetException;
30 import java.lang.reflect.Method;
31 import java.lang.reflect.Proxy;
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 /**
36  * This Doclet filters out all classes, methods, fields, etc. that have the {@code @hide} annotation
37  * on them.
38  */
39 public class FilterDoclet extends com.sun.tools.doclets.standard.Standard {
main(String[] args)40     public static void main(String[] args) throws FileNotFoundException {
41         String name = FilterDoclet.class.getName();
42         Main.execute(name, args);
43     }
44 
start(RootDoc rootDoc)45     public static boolean start(RootDoc rootDoc) {
46         return Standard.start((RootDoc) filterHidden(rootDoc, RootDoc.class));
47     }
48 
49     /**
50      * Returns true if the given element has an @hide annotation.
51      */
hasHideAnnotation(Doc doc)52     private static boolean hasHideAnnotation(Doc doc) {
53         String comment = doc.getRawCommentText();
54         return comment.contains("@hide");
55     }
56 
57     /**
58      * Returns true if the given element is hidden.
59      */
isHidden(Doc doc)60     private static boolean isHidden(Doc doc) {
61         // Methods, fields, constructors.
62         if (doc instanceof MemberDoc) {
63             return hasHideAnnotation(doc);
64         }
65         // Classes, interfaces, enums, annotation types.
66         if (doc instanceof ClassDoc) {
67             ClassDoc classDoc = (ClassDoc) doc;
68             // Check the containing package.
69             if (hasHideAnnotation(classDoc.containingPackage())) {
70                 return true;
71             }
72             // Check the class doc and containing class docs if this is a
73             // nested class.
74             ClassDoc current = classDoc;
75             do {
76                 if (hasHideAnnotation(current)) {
77                     return true;
78                 }
79                 current = current.containingClass();
80             } while (current != null);
81         }
82         return false;
83     }
84 
85     /**
86      * Filters out hidden elements.
87      */
filterHidden(Object o, Class<?> expected)88     private static Object filterHidden(Object o, Class<?> expected) {
89         if (o == null) {
90             return null;
91         }
92 
93         Class<?> type = o.getClass();
94         if (type.getName().startsWith("com.sun.")) {
95             // TODO: Implement interfaces from superclasses, too.
96             return Proxy.newProxyInstance(
97                     type.getClassLoader(), type.getInterfaces(), new HideHandler(o));
98         } else if (o instanceof Object[]) {
99             Class<?> componentType = expected.getComponentType();
100             if (componentType == null) {
101                 return o;
102             }
103 
104             Object[] array = (Object[]) o;
105             List<Object> list = new ArrayList<Object>(array.length);
106             for (Object entry : array) {
107                 if ((entry instanceof Doc) && isHidden((Doc) entry)) {
108                     continue;
109                 }
110                 list.add(filterHidden(entry, componentType));
111             }
112             return list.toArray((Object[]) Array.newInstance(componentType, list.size()));
113         } else {
114             return o;
115         }
116     }
117 
118     /**
119      * Filters hidden elements.
120      */
121     private static class HideHandler implements InvocationHandler {
122         private final Object target;
123 
HideHandler(Object target)124         public HideHandler(Object target) {
125             this.target = target;
126         }
127 
128         @Override
invoke(Object proxy, Method method, Object[] args)129         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
130             String methodName = method.getName();
131             if (args != null) {
132                 if (methodName.equals("compareTo") || methodName.equals("equals")
133                         || methodName.equals("overrides") || methodName.equals("subclassOf")) {
134                     args[0] = unwrap(args[0]);
135                 }
136             }
137 
138             try {
139                 return filterHidden(method.invoke(target, args), method.getReturnType());
140             } catch (InvocationTargetException e) {
141                 e.printStackTrace();
142                 throw e.getTargetException();
143             }
144         }
145 
unwrap(Object proxy)146         private static Object unwrap(Object proxy) {
147             if (proxy instanceof Proxy)
148                 return ((HideHandler) Proxy.getInvocationHandler(proxy)).target;
149             return proxy;
150         }
151     }
152 }
153