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     private boolean handleTypeDeclarationNode(AbstractTypeDeclaration node) {
    118       handleDeclarationNode(node);
    119       // Continue processing for nested types / methods.
    120       return true;
    121     }
    122 
    123     private boolean handleMemberDeclarationNode(BodyDeclaration node) {
    124       handleDeclarationNode(node);
    125       // Leaf declaration (i.e. a method, fields, enum constant).
    126       return false;
    127     }
    128 
    129     private void handleDeclarationNode(BodyDeclaration node) {
    130       if (isExplicitlyHidden(node)) {
    131         return;
    132       }
    133 
    134       AbstractTypeDeclaration typeDeclaration = TypeLocator.findTypeDeclarationNode(node);
    135       if (typeDeclaration == null) {
    136         // Not unusual: methods / fields defined on anonymous types are like this. The parent
    137         // is a constructor expression, not a declaration.
    138         return;
    139       }
    140 
    141       boolean isNonTypeDeclaration = typeDeclaration != node;
    142       if (isNonTypeDeclaration) {
    143         if (isExplicitlyHidden(node) || !isMemberPublicApiEligible(typeDeclaration, node)) {
    144           return;
    145         }
    146       }
    147       while (typeDeclaration != null) {
    148         if (isExplicitlyHidden(typeDeclaration) || !isTypePublicApiEligible(typeDeclaration)) {
    149           return;
    150         }
    151         typeDeclaration = TypeLocator.findEnclosingTypeDeclaration(typeDeclaration);
    152       }
    153       // The node is appropriately public and is not hidden.
    154       publicMembers.addAll(BodyDeclarationLocators.toLocatorStringForms(node));
    155     }
    156 
    157     private boolean isTypePublicApiEligible(AbstractTypeDeclaration typeDeclaration) {
    158       int typeModifiers = typeDeclaration.getModifiers();
    159       return ((typeModifiers & Modifier.PUBLIC) != 0);
    160     }
    161 
    162     public boolean isMemberPublicApiEligible(AbstractTypeDeclaration type, BodyDeclaration node) {
    163       if (node.getNodeType() == ASTNode.ENUM_DECLARATION
    164           || node.getNodeType() == ASTNode.TYPE_DECLARATION) {
    165         throw new AssertionError("Unsupported node type: " + node);
    166       }
    167 
    168       if (type instanceof TypeDeclaration) {
    169         TypeDeclaration typeDeclaration = (TypeDeclaration) type;
    170         // All methods are public on interfaces. Not sure if true on Java 8.
    171         if (typeDeclaration.isInterface()) {
    172           return true;
    173         }
    174       }
    175       int memberModifiers = node.getModifiers();
    176       return ((memberModifiers & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0);
    177     }
    178 
    179     private boolean isExplicitlyHidden(BodyDeclaration node) {
    180       Javadoc javadoc = node.getJavadoc();
    181       if (javadoc == null) {
    182         return false;
    183       }
    184       final Boolean[] isHidden = new Boolean[] { false };
    185       javadoc.accept(new ASTVisitor(true /* visitDocNodes */) {
    186         @Override public boolean visit(TagElement node) {
    187           String tagName = node.getTagName();
    188           if (tagName == null) {
    189             return true;
    190           }
    191           if (tagName.equals("@hide")) {
    192             isHidden[0] = true;
    193             return false;
    194           }
    195           return true;
    196         }
    197       });
    198       return isHidden[0];
    199     }
    200 
    201     public List<String> publicMembers() {
    202       return publicMembers;
    203     }
    204   }
    205 }
    206