Home | History | Annotate | Download | only in checker
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      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 package com.android.icu4j.srcgen.checker;
     17 
     18 import com.google.common.collect.Lists;
     19 import com.google.currysrc.api.Rules;
     20 import com.google.currysrc.api.input.InputFileGenerator;
     21 import com.google.currysrc.api.output.NullOutputSourceFileGenerator;
     22 import com.google.currysrc.api.output.OutputSourceFileGenerator;
     23 import com.google.currysrc.api.process.Context;
     24 import com.google.currysrc.api.process.Processor;
     25 import com.google.currysrc.api.process.Rule;
     26 import com.google.currysrc.api.process.ast.BodyDeclarationLocators;
     27 import com.google.currysrc.api.process.ast.TypeLocator;
     28 
     29 import org.eclipse.jdt.core.dom.ASTNode;
     30 import org.eclipse.jdt.core.dom.ASTVisitor;
     31 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
     32 import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
     33 import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
     34 import org.eclipse.jdt.core.dom.BodyDeclaration;
     35 import org.eclipse.jdt.core.dom.CompilationUnit;
     36 import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
     37 import org.eclipse.jdt.core.dom.EnumDeclaration;
     38 import org.eclipse.jdt.core.dom.FieldDeclaration;
     39 import org.eclipse.jdt.core.dom.Javadoc;
     40 import org.eclipse.jdt.core.dom.MethodDeclaration;
     41 import org.eclipse.jdt.core.dom.Modifier;
     42 import org.eclipse.jdt.core.dom.TagElement;
     43 import org.eclipse.jdt.core.dom.TypeDeclaration;
     44 
     45 import java.io.File;
     46 import java.util.Collections;
     47 import java.util.List;
     48 
     49 import static com.android.icu4j.srcgen.Icu4jTransformRules.createOptionalRule;
     50 
     51 /**
     52  * Rules that operate over a set of files and record the public API (according to Android's rules
     53  * for @hide).
     54  */
     55 class RecordPublicApiRules implements Rules {
     56 
     57   private final InputFileGenerator inputFileGenerator;
     58 
     59   private final RecordPublicApi recordPublicApi;
     60 
     61   public RecordPublicApiRules(InputFileGenerator inputFileGenerator) {
     62     this.inputFileGenerator = inputFileGenerator;
     63     recordPublicApi = new RecordPublicApi();
     64   }
     65 
     66   @Override public InputFileGenerator getInputFileGenerator() {
     67     return inputFileGenerator;
     68   }
     69 
     70   @Override public List<Rule> getRuleList(File file) {
     71     return Collections.<Rule>singletonList(createOptionalRule(recordPublicApi));
     72   }
     73 
     74   @Override public OutputSourceFileGenerator getOutputSourceFileGenerator() {
     75     return NullOutputSourceFileGenerator.INSTANCE;
     76   }
     77 
     78   public List<String> publicMembers() {
     79     return recordPublicApi.publicMembers();
     80   }
     81 
     82   private static class RecordPublicApi implements Processor {
     83     private final List<String> publicMembers = Lists.newArrayList();
     84 
     85     @Override public void process(Context context, CompilationUnit cu) {
     86       cu.accept(new ASTVisitor() {
     87         @Override public boolean visit(AnnotationTypeDeclaration node) {
     88           throw new AssertionError("Not supported");
     89         }
     90 
     91         @Override public boolean visit(AnnotationTypeMemberDeclaration node) {
     92           throw new AssertionError("Not supported");
     93         }
     94 
     95         @Override public boolean visit(EnumConstantDeclaration node) {
     96           return handleMemberDeclarationNode(node);
     97         }
     98 
     99         @Override public boolean visit(EnumDeclaration node) {
    100           return handleTypeDeclarationNode(node);
    101         }
    102 
    103         @Override public boolean visit(FieldDeclaration node) {
    104           return handleMemberDeclarationNode(node);
    105         }
    106 
    107         @Override public boolean visit(MethodDeclaration node) {
    108           return handleMemberDeclarationNode(node);
    109         }
    110 
    111         @Override public boolean visit(TypeDeclaration node) {
    112           return handleTypeDeclarationNode(node);
    113         }
    114       });
    115     }
    116 
    117     @Override
    118     public String toString() {
    119       return "RecordPublicApi{}";
    120     }
    121 
    122     private boolean handleTypeDeclarationNode(AbstractTypeDeclaration node) {
    123       handleDeclarationNode(node);
    124       // Continue processing for nested types / methods.
    125       return true;
    126     }
    127 
    128     private boolean handleMemberDeclarationNode(BodyDeclaration node) {
    129       handleDeclarationNode(node);
    130       // Leaf declaration (i.e. a method, fields, enum constant).
    131       return false;
    132     }
    133 
    134     private void handleDeclarationNode(BodyDeclaration node) {
    135       if (isExplicitlyHidden(node)) {
    136         return;
    137       }
    138 
    139       AbstractTypeDeclaration typeDeclaration = TypeLocator.findTypeDeclarationNode(node);
    140       if (typeDeclaration == null) {
    141         // Not unusual: methods / fields defined on anonymous types are like this. The parent
    142         // is a constructor expression, not a declaration.
    143         return;
    144       }
    145 
    146       boolean isNonTypeDeclaration = typeDeclaration != node;
    147       if (isNonTypeDeclaration) {
    148         if (isExplicitlyHidden(node) || !isMemberPublicApiEligible(typeDeclaration, node)) {
    149           return;
    150         }
    151       }
    152       while (typeDeclaration != null) {
    153         if (isExplicitlyHidden(typeDeclaration) || !isTypePublicApiEligible(typeDeclaration)) {
    154           return;
    155         }
    156         typeDeclaration = TypeLocator.findEnclosingTypeDeclaration(typeDeclaration);
    157       }
    158       // The node is appropriately public and is not hidden.
    159       publicMembers.addAll(BodyDeclarationLocators.toLocatorStringForms(node));
    160     }
    161 
    162     private boolean isTypePublicApiEligible(AbstractTypeDeclaration typeDeclaration) {
    163       int typeModifiers = typeDeclaration.getModifiers();
    164       return ((typeModifiers & Modifier.PUBLIC) != 0);
    165     }
    166 
    167     public boolean isMemberPublicApiEligible(AbstractTypeDeclaration type, BodyDeclaration node) {
    168       if (node.getNodeType() == ASTNode.ENUM_DECLARATION
    169           || node.getNodeType() == ASTNode.TYPE_DECLARATION) {
    170         throw new AssertionError("Unsupported node type: " + node);
    171       }
    172 
    173       if (type instanceof TypeDeclaration) {
    174         TypeDeclaration typeDeclaration = (TypeDeclaration) type;
    175         // All methods are public on interfaces. Not sure if true on Java 8.
    176         if (typeDeclaration.isInterface()) {
    177           return true;
    178         }
    179       }
    180       int memberModifiers = node.getModifiers();
    181       return ((memberModifiers & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0);
    182     }
    183 
    184     private boolean isExplicitlyHidden(BodyDeclaration node) {
    185       Javadoc javadoc = node.getJavadoc();
    186       if (javadoc == null) {
    187         return false;
    188       }
    189       final Boolean[] isHidden = new Boolean[] { false };
    190       javadoc.accept(new ASTVisitor(true /* visitDocNodes */) {
    191         @Override public boolean visit(TagElement node) {
    192           String tagName = node.getTagName();
    193           if (tagName == null) {
    194             return true;
    195           }
    196           if (tagName.equals("@hide")) {
    197             isHidden[0] = true;
    198             return false;
    199           }
    200           return true;
    201         }
    202       });
    203       return isHidden[0];
    204     }
    205 
    206     public List<String> publicMembers() {
    207       return publicMembers;
    208     }
    209   }
    210 }
    211