Home | History | Annotate | Download | only in srcgen
      1 /*
      2  * Copyright (C) 2016 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;
     17 
     18 import com.google.currysrc.api.process.Context;
     19 import com.google.currysrc.api.process.Processor;
     20 import java.lang.reflect.Modifier;
     21 import java.util.List;
     22 import java.util.Map;
     23 import java.util.TreeMap;
     24 import org.eclipse.jdt.core.dom.AST;
     25 import org.eclipse.jdt.core.dom.CompilationUnit;
     26 import org.eclipse.jdt.core.dom.ImportDeclaration;
     27 import org.eclipse.jdt.core.dom.Name;
     28 import org.eclipse.jdt.core.dom.QualifiedName;
     29 import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
     30 import org.eclipse.jdt.core.dom.Type;
     31 import org.eclipse.jdt.core.dom.TypeDeclaration;
     32 import org.eclipse.jdt.core.dom.TypeLiteral;
     33 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
     34 import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
     35 
     36 /**
     37  * Adds @RunWith annotations to test classes.
     38  *
     39  * <p>A class that extends {@code TestFmwk.TestGroup} will have an annotation
     40  * {@code @RunWith(IcuTestGroupRunner.class)} added and a class that extends {@code TestFmwk} will
     41  * have an annotation {@code @RunWith(IcuTestFmwkRunner.class)} added.
     42  *
     43  * <p>Ideally, this would operate on an AST that has resolved type information so that it was
     44  * possible to traverse the class hierarchy to identify classes that are derived both directly and
     45  * indirectly from the test classes. Unfortunately, that approach is very, very time consuming, an
     46  * order of magnitude (if not two) slower than not resolving the types. Therefore, it was quicker
     47  * to simply iteratively annotate and run the tests to find the set of direct dependencies and hard
     48  * code them in here. That could be an issue if this had to deal with many changes in the test
     49  * classes but this code should only have a very short lifespan given that the ICU4J team is
     50  * already well on their way to porting the tests over to JUnit.
     51  */
     52 class RunWithAnnotator implements Processor {
     53 
     54     private static final String RUN_WITH_CLASS_NAME = "org.junit.runner.RunWith";
     55 
     56     private static final String[] TEST_FMWK_BASE_CLASSES = {
     57             "BidiTest",
     58             "CalendarTest",
     59             "android.icu.dev.test.TestFmwk",
     60             "CompatibilityTest",
     61             "CoverageTest",
     62             "LanguageTestRoot",
     63             "ModuleTest",
     64             "TestFmwk",
     65             "TestFmwk.TestGroup",
     66             "TestGroup",
     67             "TransliteratorTest",
     68     };
     69 
     70     private static final String[] TEST_GROUP_BASE_CLASSES = {
     71             "TestFmwk.TestGroup",
     72             "TestGroup",
     73     };
     74 
     75     private static Map<String, String> BASE_CLASS_2_RUNNER_CLASS = new TreeMap<>();
     76     static {
     77         for (String name : TEST_FMWK_BASE_CLASSES) {
     78             BASE_CLASS_2_RUNNER_CLASS.put(name, "android.icu.junit.IcuTestFmwkRunner");
     79         }
     80         for (String name : TEST_GROUP_BASE_CLASSES) {
     81             BASE_CLASS_2_RUNNER_CLASS.put(name, "android.icu.junit.IcuTestGroupRunner");
     82         }
     83     }
     84 
     85     @Override
     86     public void process(Context context, CompilationUnit cu) {
     87         List types = cu.types();
     88         ASTRewrite rewrite = context.rewrite();
     89         boolean imported = false;
     90         for (Object type : types) {
     91             if (type instanceof TypeDeclaration) {
     92                 TypeDeclaration typeDeclaration = (TypeDeclaration) type;
     93                 imported = annotateTypeDeclaration(cu, rewrite, typeDeclaration, true, imported);
     94             }
     95         }
     96     }
     97 
     98     private boolean annotateTypeDeclaration(CompilationUnit cu,
     99             ASTRewrite rewrite, TypeDeclaration typeDeclaration, boolean topLevelClass,
    100             boolean imported) {
    101 
    102         int modifiers = typeDeclaration.getModifiers();
    103         if ((topLevelClass || Modifier.isStatic(modifiers)) && Modifier.isPublic(modifiers)
    104                 && !Modifier.isAbstract(modifiers)) {
    105             Type superClassType = typeDeclaration.getSuperclassType();
    106             if (superClassType != null) {
    107                 String name = superClassType.toString();
    108                 String runnerClass = BASE_CLASS_2_RUNNER_CLASS.get(name);
    109                 if (runnerClass != null) {
    110                     addRunWithAnnotation(cu, rewrite, typeDeclaration, runnerClass, imported);
    111                     imported = true;
    112                 }
    113             }
    114         }
    115 
    116         for (TypeDeclaration innerClass : typeDeclaration.getTypes()) {
    117             imported = annotateTypeDeclaration(cu, rewrite, innerClass, false, imported);
    118         }
    119 
    120         return imported;
    121     }
    122 
    123     private boolean addRunWithAnnotation(
    124             CompilationUnit cu, ASTRewrite rewrite, TypeDeclaration type, String runnerClass,
    125             boolean imported) {
    126 
    127         AST ast = cu.getAST();
    128 
    129         QualifiedName qRunWith = (QualifiedName) ast.newName(RUN_WITH_CLASS_NAME);
    130         QualifiedName qRunner = (QualifiedName) ast.newName(runnerClass);
    131         if (!imported) {
    132             appendImport(cu, rewrite, qRunWith);
    133             appendImport(cu, rewrite, qRunner);
    134         }
    135         String runWithName = qRunWith.getName().getIdentifier();
    136         String runnerName = qRunner.getName().getIdentifier();
    137 
    138         SingleMemberAnnotation annotation = ast.newSingleMemberAnnotation();
    139         annotation.setTypeName(ast.newSimpleName(runWithName));
    140 
    141         TypeLiteral junit4Literal = ast.newTypeLiteral();
    142         junit4Literal.setType(ast.newSimpleType(ast.newSimpleName(runnerName)));
    143         annotation.setValue(junit4Literal);
    144 
    145         ListRewrite lrw = rewrite.getListRewrite(type, type.getModifiersProperty());
    146         lrw.insertFirst(annotation, null);
    147 
    148         return imported;
    149     }
    150 
    151     private void appendImport(CompilationUnit cu, ASTRewrite rewriter, Name name) {
    152         ListRewrite lrw = rewriter.getListRewrite(cu, CompilationUnit.IMPORTS_PROPERTY);
    153         AST ast = cu.getAST();
    154         ImportDeclaration importDeclaration = ast.newImportDeclaration();
    155         importDeclaration.setName(name);
    156         lrw.insertLast(importDeclaration, null);
    157     }
    158 
    159 }
    160