1 /*
2  * Copyright (C) 2018 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.google.currysrc.processors;
17 
18 import com.google.currysrc.api.process.Context;
19 import com.google.currysrc.api.process.Processor;
20 import com.google.currysrc.api.process.ast.TypeLocator;
21 import java.util.List;
22 import org.eclipse.jdt.core.dom.AST;
23 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
24 import org.eclipse.jdt.core.dom.CompilationUnit;
25 import org.eclipse.jdt.core.dom.FieldDeclaration;
26 import org.eclipse.jdt.core.dom.MethodDeclaration;
27 import org.eclipse.jdt.core.dom.Modifier;
28 import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
29 import org.eclipse.jdt.core.dom.TypeDeclaration;
30 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
31 import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
32 
33 /**
34  * Add a default constructor to a white list of classes.
35  */
36 public class AddDefaultConstructor implements Processor {
37 
38   /**
39    * The visibility modifier keywords.
40    */
41   private static final ModifierKeyword[] VISIBILITY_MODIFIERS = new ModifierKeyword[]{
42       ModifierKeyword.PUBLIC_KEYWORD,
43       ModifierKeyword.PRIVATE_KEYWORD, ModifierKeyword.PROTECTED_KEYWORD};
44 
45   private final List<TypeLocator> whitelist;
46   private Listener listener;
47 
48   public interface Listener {
49 
50     /**
51      * Called when a default constructor is added to a class.
52      *
53      * @param locator the locator for the modified class.
54      * @param typeDeclaration the class that was modified.
55      */
onAddDefaultConstructor(TypeLocator locator, TypeDeclaration typeDeclaration)56     void onAddDefaultConstructor(TypeLocator locator, TypeDeclaration typeDeclaration);
57   }
58 
AddDefaultConstructor(List<TypeLocator> whitelist)59   public AddDefaultConstructor(List<TypeLocator> whitelist) {
60     this.whitelist = whitelist;
61     this.listener = (l, typeDeclaration) -> {};
62   }
63 
setListener(Listener listener)64   public void setListener(Listener listener) {
65     this.listener = listener;
66   }
67 
68   @Override
process(Context context, CompilationUnit cu)69   public void process(Context context, CompilationUnit cu) {
70     final ASTRewrite rewrite = context.rewrite();
71     for (TypeLocator typeLocator : whitelist) {
72       AbstractTypeDeclaration abstractTypeDeclaration = typeLocator.find(cu);
73       if (abstractTypeDeclaration instanceof TypeDeclaration) {
74         TypeDeclaration typeDeclaration = (TypeDeclaration) abstractTypeDeclaration;
75         addDefaultConstructor(rewrite, typeDeclaration);
76         listener.onAddDefaultConstructor(typeLocator, typeDeclaration);
77       }
78     }
79   }
80 
addDefaultConstructor(ASTRewrite rewrite, TypeDeclaration node)81   private void addDefaultConstructor(ASTRewrite rewrite, TypeDeclaration node) {
82     AST ast = rewrite.getAST();
83     MethodDeclaration method = ast.newMethodDeclaration();
84     method.setConstructor(true);
85     // Copy the class name as a constructor must have the same name as the class.
86     method.setName(ast.newSimpleName(node.getName().getIdentifier()));
87 
88     // The default constructor has the same visibility as its containing class.
89     int modifiers = node.getModifiers();
90     @SuppressWarnings("unchecked") List<Modifier> methodModifiers = method.modifiers();
91     for (ModifierKeyword keyword : VISIBILITY_MODIFIERS) {
92       if ((modifiers & keyword.toFlagValue()) != 0) {
93         methodModifiers.add(ast.newModifier(keyword));
94       }
95     }
96 
97     // Create an empty body.
98     method.setBody(ast.newBlock());
99 
100     ListRewrite listRewrite = rewrite.getListRewrite(node, node.getBodyDeclarationsProperty());
101 
102     MethodDeclaration[] methods = node.getMethods();
103     if (methods.length > 0) {
104       // Insert before the first method.
105       listRewrite.insertBefore(method, methods[0], null);
106     } else {
107       FieldDeclaration[] fields = node.getFields();
108       if (fields.length > 0) {
109         // Insert after the last field.
110         listRewrite.insertAfter(method, fields[fields.length - 1], null);
111       } else {
112         // Insert at the beginning of the class.
113         listRewrite.insertFirst(method, null);
114       }
115     }
116   }
117 }
118