Home | History | Annotate | Download | only in annotool
      1 /*
      2  * Copyright (C) 2008 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 
     17 package com.android.dx.command.annotool;
     18 
     19 import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations;
     20 import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations;
     21 import com.android.dx.cf.attrib.BaseAnnotations;
     22 import com.android.dx.cf.direct.ClassPathOpener;
     23 import com.android.dx.cf.direct.DirectClassFile;
     24 import com.android.dx.cf.direct.StdAttributeFactory;
     25 import com.android.dx.cf.iface.Attribute;
     26 import com.android.dx.cf.iface.AttributeList;
     27 import com.android.dx.rop.annotation.Annotation;
     28 import com.android.dx.util.ByteArray;
     29 import java.io.File;
     30 import java.lang.annotation.ElementType;
     31 import java.util.HashSet;
     32 
     33 /**
     34  * Greps annotations on a set of class files and prints matching elements
     35  * to stdout. What counts as a match and what should be printed is controlled
     36  * by the {@code Main.Arguments} instance.
     37  */
     38 class AnnotationLister {
     39     /**
     40      * The string name of the pseudo-class that
     41      * contains package-wide annotations
     42      */
     43     private static final String PACKAGE_INFO = "package-info";
     44 
     45     /** current match configuration */
     46     private final Main.Arguments args;
     47 
     48     /** Set of classes whose inner classes should be considered matched */
     49     HashSet<String> matchInnerClassesOf = new HashSet<String>();
     50 
     51     /** set of packages whose classes should be considered matched */
     52     HashSet<String> matchPackages = new HashSet<String>();
     53 
     54     AnnotationLister (Main.Arguments args) {
     55         this.args = args;
     56     }
     57 
     58     /** Processes based on configuration specified in constructor. */
     59     void process() {
     60         for (String path : args.files) {
     61             ClassPathOpener opener;
     62 
     63             opener = new ClassPathOpener(path, true,
     64                     new ClassPathOpener.Consumer() {
     65                 @Override
     66                 public boolean processFileBytes(String name, long lastModified, byte[] bytes) {
     67                     if (!name.endsWith(".class")) {
     68                         return true;
     69                     }
     70 
     71                     ByteArray ba = new ByteArray(bytes);
     72                     DirectClassFile cf
     73                         = new DirectClassFile(ba, name, true);
     74 
     75                     cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
     76                     AttributeList attributes = cf.getAttributes();
     77                     Attribute att;
     78 
     79                     String cfClassName
     80                             = cf.getThisClass().getClassType().getClassName();
     81 
     82                     if (cfClassName.endsWith(PACKAGE_INFO)) {
     83                         att = attributes.findFirst(
     84                                 AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME);
     85 
     86                         for (;att != null; att = attributes.findNext(att)) {
     87                             BaseAnnotations ann = (BaseAnnotations)att;
     88                             visitPackageAnnotation(cf, ann);
     89                         }
     90 
     91                         att = attributes.findFirst(
     92                                 AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
     93 
     94                         for (;att != null; att = attributes.findNext(att)) {
     95                             BaseAnnotations ann = (BaseAnnotations)att;
     96                             visitPackageAnnotation(cf, ann);
     97                         }
     98                     } else if (isMatchingInnerClass(cfClassName)
     99                             || isMatchingPackage(cfClassName)) {
    100                         printMatch(cf);
    101                     } else {
    102                         att = attributes.findFirst(
    103                                 AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME);
    104 
    105                         for (;att != null; att = attributes.findNext(att)) {
    106                             BaseAnnotations ann = (BaseAnnotations)att;
    107                             visitClassAnnotation(cf, ann);
    108                         }
    109 
    110                         att = attributes.findFirst(
    111                                 AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
    112 
    113                         for (;att != null; att = attributes.findNext(att)) {
    114                             BaseAnnotations ann = (BaseAnnotations)att;
    115                             visitClassAnnotation(cf, ann);
    116                         }
    117                     }
    118 
    119                     return true;
    120                 }
    121 
    122                 @Override
    123                 public void onException(Exception ex) {
    124                     throw new RuntimeException(ex);
    125                 }
    126 
    127                 @Override
    128                 public void onProcessArchiveStart(File file) {
    129 
    130                 }
    131 
    132             });
    133 
    134             opener.process();
    135         }
    136     }
    137 
    138     /**
    139      * Inspects a class annotation.
    140      *
    141      * @param cf {@code non-null;} class file
    142      * @param ann {@code non-null;} annotation
    143      */
    144     private void visitClassAnnotation(DirectClassFile cf,
    145             BaseAnnotations ann) {
    146 
    147         if (!args.eTypes.contains(ElementType.TYPE)) {
    148             return;
    149         }
    150 
    151         for (Annotation anAnn : ann.getAnnotations().getAnnotations()) {
    152             String annClassName
    153                     = anAnn.getType().getClassType().getClassName();
    154             if (args.aclass.equals(annClassName)) {
    155                 printMatch(cf);
    156             }
    157         }
    158     }
    159 
    160     /**
    161      * Inspects a package annotation
    162      *
    163      * @param cf {@code non-null;} class file of "package-info" pseudo-class
    164      * @param ann {@code non-null;} annotation
    165      */
    166     private void visitPackageAnnotation(
    167             DirectClassFile cf, BaseAnnotations ann) {
    168 
    169         if (!args.eTypes.contains(ElementType.PACKAGE)) {
    170             return;
    171         }
    172 
    173         String packageName = cf.getThisClass().getClassType().getClassName();
    174 
    175         int slashIndex = packageName.lastIndexOf('/');
    176 
    177         if (slashIndex == -1) {
    178             packageName = "";
    179         } else {
    180             packageName
    181                     = packageName.substring(0, slashIndex);
    182         }
    183 
    184 
    185         for (Annotation anAnn : ann.getAnnotations().getAnnotations()) {
    186             String annClassName
    187                     = anAnn.getType().getClassType().getClassName();
    188             if (args.aclass.equals(annClassName)) {
    189                 printMatchPackage(packageName);
    190             }
    191         }
    192     }
    193 
    194 
    195     /**
    196      * Prints, or schedules for printing, elements related to a
    197      * matching package.
    198      *
    199      * @param packageName {@code non-null;} name of package
    200      */
    201     private void printMatchPackage(String packageName) {
    202         for (Main.PrintType pt : args.printTypes) {
    203             switch (pt) {
    204                 case CLASS:
    205                 case INNERCLASS:
    206                 case METHOD:
    207                     matchPackages.add(packageName);
    208                     break;
    209                 case PACKAGE:
    210                     System.out.println(packageName.replace('/','.'));
    211                     break;
    212             }
    213         }
    214     }
    215 
    216     /**
    217      * Prints, or schedules for printing, elements related to a matching
    218      * class.
    219      *
    220      * @param cf {@code non-null;} matching class
    221      */
    222     private void printMatch(DirectClassFile cf) {
    223         for (Main.PrintType pt : args.printTypes) {
    224             switch (pt) {
    225                 case CLASS:
    226                     String classname;
    227                     classname =
    228                         cf.getThisClass().getClassType().getClassName();
    229                     classname = classname.replace('/','.');
    230                     System.out.println(classname);
    231                     break;
    232                 case INNERCLASS:
    233                     matchInnerClassesOf.add(
    234                             cf.getThisClass().getClassType().getClassName());
    235                     break;
    236                 case METHOD:
    237                     //TODO
    238                     break;
    239                 case PACKAGE:
    240                     break;
    241             }
    242         }
    243     }
    244 
    245     /**
    246      * Checks to see if a specified class name should be considered a match
    247      * due to previous matches.
    248      *
    249      * @param s {@code non-null;} class name
    250      * @return true if this class should be considered a match
    251      */
    252     private boolean isMatchingInnerClass(String s) {
    253         int i;
    254 
    255         while (0 < (i = s.lastIndexOf('$'))) {
    256             s = s.substring(0, i);
    257             if (matchInnerClassesOf.contains(s)) {
    258                 return true;
    259             }
    260         }
    261 
    262         return false;
    263     }
    264 
    265     /**
    266      * Checks to see if a specified package should be considered a match due
    267      * to previous matches.
    268      *
    269      * @param s {@code non-null;} package name
    270      * @return true if this package should be considered a match
    271      */
    272     private boolean isMatchingPackage(String s) {
    273         int slashIndex = s.lastIndexOf('/');
    274 
    275         String packageName;
    276         if (slashIndex == -1) {
    277             packageName = "";
    278         } else {
    279             packageName
    280                     = s.substring(0, slashIndex);
    281         }
    282 
    283         return matchPackages.contains(packageName);
    284     }
    285 }
    286