/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.classification.tools; import com.sun.javadoc.AnnotationDesc; import com.sun.javadoc.AnnotationTypeDoc; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.ConstructorDoc; import com.sun.javadoc.Doc; import com.sun.javadoc.FieldDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.PackageDoc; import com.sun.javadoc.ProgramElementDoc; import com.sun.javadoc.RootDoc; import java.lang.reflect.Array; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; /** * Process the {@link RootDoc} by substituting with (nested) proxy objects that * exclude elements with Private or LimitedPrivate annotations. * <p> * Based on code from http://www.sixlegs.com/blog/java/exclude-javadoc-tag.html. */ class RootDocProcessor { static String stability = StabilityOptions.UNSTABLE_OPTION; static boolean treatUnannotatedClassesAsPrivate = false; public static RootDoc process(RootDoc root) { return (RootDoc) process(root, RootDoc.class); } private static Object process(Object obj, Class<?> type) { if (obj == null) { return null; } Class<?> cls = obj.getClass(); if (cls.getName().startsWith("com.sun.")) { return getProxy(obj); } else if (obj instanceof Object[]) { Class<?> componentType = type.isArray() ? type.getComponentType() : cls.getComponentType(); Object[] array = (Object[]) obj; Object[] newArray = (Object[]) Array.newInstance(componentType, array.length); for (int i = 0; i < array.length; ++i) { newArray[i] = process(array[i], componentType); } return newArray; } return obj; } private static Map<Object, Object> proxies = new WeakHashMap<Object, Object>(); private static Object getProxy(Object obj) { Object proxy = proxies.get(obj); if (proxy == null) { proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new ExcludeHandler(obj)); proxies.put(obj, proxy); } return proxy; } private static class ExcludeHandler implements InvocationHandler { private Object target; public ExcludeHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (target instanceof Doc) { if (methodName.equals("isIncluded")) { Doc doc = (Doc) target; return !exclude(doc) && doc.isIncluded(); } if (target instanceof RootDoc) { if (methodName.equals("classes")) { return filter(((RootDoc) target).classes(), ClassDoc.class); } else if (methodName.equals("specifiedClasses")) { return filter(((RootDoc) target).specifiedClasses(), ClassDoc.class); } else if (methodName.equals("specifiedPackages")) { return filter(((RootDoc) target).specifiedPackages(), PackageDoc.class); } } else if (target instanceof ClassDoc) { if (isFiltered(args)) { if (methodName.equals("methods")) { return filter(((ClassDoc) target).methods(true), MethodDoc.class); } else if (methodName.equals("fields")) { return filter(((ClassDoc) target).fields(true), FieldDoc.class); } else if (methodName.equals("innerClasses")) { return filter(((ClassDoc) target).innerClasses(true), ClassDoc.class); } else if (methodName.equals("constructors")) { return filter(((ClassDoc) target).constructors(true), ConstructorDoc.class); } } } else if (target instanceof PackageDoc) { if (methodName.equals("allClasses")) { if (isFiltered(args)) { return filter(((PackageDoc) target).allClasses(true), ClassDoc.class); } else { return filter(((PackageDoc) target).allClasses(), ClassDoc.class); } } else if (methodName.equals("annotationTypes")) { return filter(((PackageDoc) target).annotationTypes(), AnnotationTypeDoc.class); } else if (methodName.equals("enums")) { return filter(((PackageDoc) target).enums(), ClassDoc.class); } else if (methodName.equals("errors")) { return filter(((PackageDoc) target).errors(), ClassDoc.class); } else if (methodName.equals("exceptions")) { return filter(((PackageDoc) target).exceptions(), ClassDoc.class); } else if (methodName.equals("interfaces")) { return filter(((PackageDoc) target).interfaces(), ClassDoc.class); } else if (methodName.equals("ordinaryClasses")) { return filter(((PackageDoc) target).ordinaryClasses(), ClassDoc.class); } } } if (args != null) { if (methodName.equals("compareTo") || methodName.equals("equals") || methodName.equals("overrides") || methodName.equals("subclassOf")) { args[0] = unwrap(args[0]); } } try { return process(method.invoke(target, args), method.getReturnType()); } catch (InvocationTargetException e) { throw e.getTargetException(); } } private static boolean exclude(Doc doc) { AnnotationDesc[] annotations = null; if (doc instanceof ProgramElementDoc) { annotations = ((ProgramElementDoc) doc).annotations(); } else if (doc instanceof PackageDoc) { annotations = ((PackageDoc) doc).annotations(); } if (annotations != null) { for (AnnotationDesc annotation : annotations) { String qualifiedTypeName = annotation.annotationType().qualifiedTypeName(); if (qualifiedTypeName.equals( InterfaceAudience.Private.class.getCanonicalName()) || qualifiedTypeName.equals( InterfaceAudience.LimitedPrivate.class.getCanonicalName())) { return true; } if (stability.equals(StabilityOptions.EVOLVING_OPTION)) { if (qualifiedTypeName.equals( InterfaceStability.Unstable.class.getCanonicalName())) { return true; } } if (stability.equals(StabilityOptions.STABLE_OPTION)) { if (qualifiedTypeName.equals( InterfaceStability.Unstable.class.getCanonicalName()) || qualifiedTypeName.equals( InterfaceStability.Evolving.class.getCanonicalName())) { return true; } } } for (AnnotationDesc annotation : annotations) { String qualifiedTypeName = annotation.annotationType().qualifiedTypeName(); if (qualifiedTypeName.equals( InterfaceAudience.Public.class.getCanonicalName())) { return false; } } } if (treatUnannotatedClassesAsPrivate) { return doc.isClass() || doc.isInterface() || doc.isAnnotationType(); } return false; } private static Object[] filter(Doc[] array, Class<?> componentType) { if (array == null || array.length == 0) { return array; } List<Object> list = new ArrayList<Object>(array.length); for (Doc entry : array) { if (!exclude(entry)) { list.add(process(entry, componentType)); } } return list.toArray((Object[]) Array.newInstance(componentType, list .size())); } private Object unwrap(Object proxy) { if (proxy instanceof Proxy) return ((ExcludeHandler) Proxy.getInvocationHandler(proxy)).target; return proxy; } private boolean isFiltered(Object[] args) { return args != null && Boolean.TRUE.equals(args[0]); } } }