summaryrefslogtreecommitdiff
path: root/src/plugins/android.codeutils/src/com/motorola/studio/android/generateviewbylayout/GenerateCodeBasedOnLayoutVisitor.java
blob: e4b0dd014d1d9beb2cf4d371d3296dd807cf9166 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.motorola.studio.android.generateviewbylayout;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;

import com.motorola.studio.android.codeutils.i18n.CodeUtilsNLS;
import com.motorola.studio.android.generatecode.BasicCodeVisitor;
import com.motorola.studio.android.generateviewbylayout.model.CodeGeneratorDataBasedOnLayout;
import com.motorola.studio.android.generateviewbylayout.model.LayoutNode;

/**
 * Visitor for class method declarations to find onCreate methods inside activity / fragment.
 * It calls BodyVisitor to continue extracting information about the Android code.
 */
public class GenerateCodeBasedOnLayoutVisitor extends BasicCodeVisitor
{
    /*
     * Constants 
     */
    private static final String ACTIVITY_ON_CREATE_DECLARATION = "void onCreate(android.os.Bundle)"; //$NON-NLS-1$

    private static final String FRAGMENT_ON_CREATE_DECLARATION =
            "android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)"; //$NON-NLS-1$

    private static final String ACTIVITY_ON_CREATE = "onCreate"; //$NON-NLS-1$

    private static final String FRAGMENT_ON_CREATE = "onCreateView"; //$NON-NLS-1$

    private static final String ACTIVITY_ON_PAUSE_DECLARATION = "void onPause()";

    private static final String ACTIVITY_ON_RESUME_DECLARATION = "void onResume()";

    private static final String ACTIVITY_ON_PAUSE = "onPause";

    private static final String ACTIVITY_ON_RESUME = "onResume";

    private MethodDeclaration onCreateDeclaration;

    private CodeGeneratorDataBasedOnLayout.TYPE typeAssociatedToLayout;

    /**
     * If type is fragment, there may be an inflated view name.
     * This will be used to call findViewById inside fragments  
     */
    private String inflatedViewName;

    private final Set<String> declaredViewIds = new HashSet<String>();

    private final Set<String> savedViewIds = new HashSet<String>();

    private final Set<String> restoredViewIds = new HashSet<String>();

    private String layoutName;

    /**
     * @return method declaration reference if there an onCreate method declared, null if not found
     */
    public MethodDeclaration getOnCreateDeclaration()
    {
        return onCreateDeclaration;
    }

    public void setOnCreateDeclaration(MethodDeclaration onCreateDeclaration)
    {

        this.onCreateDeclaration = onCreateDeclaration;
    }

    /**
     * @return name of the layout being visited
     */
    public String getLayoutName()
    {
        return layoutName;
    }

    public void setLayoutName(String layoutName)
    {
        this.layoutName = layoutName;
    }

    /**
     * Visit method declaration, searching for instructions 
     * onCreate for activity or fragment
     */
    @Override
    public boolean visit(MethodDeclaration node)
    {
        //Fill Method information
        SimpleName name = node.getName();
        if (name.getIdentifier().equals(ACTIVITY_ON_CREATE)
                || name.getIdentifier().equals(FRAGMENT_ON_CREATE))
        {
            IMethodBinding binding = node.resolveBinding();
            if (binding != null)
            {
                if (binding.toString().trim().contains(ACTIVITY_ON_CREATE_DECLARATION))
                {
                    visitMethodBodyToIdentifyLayout(node);
                }
                else if (binding.toString().trim().contains(FRAGMENT_ON_CREATE_DECLARATION))
                {
                    if (node.getBody().statements().size() <= 1)
                    {
                        throw new IllegalArgumentException(
                                CodeUtilsNLS.MethodVisitor_InvalidFormatForFragmentOnCreateView);
                    }
                    else
                    {
                        visitMethodBodyToIdentifyLayout(node);
                    }
                }
                else
                {
                    //for each method visit to identify views already declared
                    visitToIdentifyViewsAlreadyDeclared(node);
                }
            }
        }
        else if (name.getIdentifier().equals(ACTIVITY_ON_PAUSE)
                || name.getIdentifier().equals(ACTIVITY_ON_RESUME))
        {
            IMethodBinding binding = node.resolveBinding();
            if (binding != null)
            {
                //find declared save state
                if (binding.toString().trim().contains(ACTIVITY_ON_PAUSE_DECLARATION))
                {
                    findSavedViews(node);
                }
                //find declared restore state
                else if (binding.toString().trim().contains(ACTIVITY_ON_RESUME_DECLARATION))
                {
                    findRestoredViews(node);
                }

            }
        }
        else
        {
            //for each method visit to identify views already declared
            visitToIdentifyViewsAlreadyDeclared(node);
        }
        return super.visit(node);
    }

    private void findRestoredViews(MethodDeclaration node)
    {
        SaveStateVisitor visitor = new SaveStateVisitor();
        node.accept(visitor);
        restoredViewIds.addAll(visitor.getViewIds());
    }

    private void findSavedViews(MethodDeclaration node)
    {
        SaveStateVisitor visitor = new SaveStateVisitor();
        node.accept(visitor);
        savedViewIds.addAll(visitor.getViewIds());
    }

    /**
     * @param node
     */
    protected synchronized void visitToIdentifyViewsAlreadyDeclared(MethodDeclaration node)
    {
        Block body = node.getBody();
        if (body != null)
        {
            MethodBodyVisitor visitor = new MethodBodyVisitor();
            visitAndUpdateDeclaredViewsBasedOnFindViewById(body, visitor);
        }
    }

    /**
     * Visit method body from onCreate declaration to identify layout used
     * (it also verifies views already declared)
     * @param node
     * @throws JavaModelException 
     */
    protected void visitMethodBodyToIdentifyLayout(MethodDeclaration node)
    {
        //Navigate through statements...
        setOnCreateDeclaration(node);
        Block body = node.getBody();
        if (body != null)
        {
            identifyLayout(body);
        }
    }

    /**
     * Navigates in a Block and extract layout name, if class associated to layout is activity or fragment, 
     * and, in case of fragment only, the name of the view inflated.
     */
    private void identifyLayout(Block body)
    {
        MethodBodyVisitor visitor = new MethodBodyVisitor();
        visitAndUpdateDeclaredViewsBasedOnFindViewById(body, visitor);
        setLayoutName(visitor.getLayoutName());
        typeAssociatedToLayout = visitor.getTypeAssociatedToLayout();
        setInflatedViewName(visitor.getInflatedViewName());
    }

    /**
     * Visit method body to identify view ids already declared
     * @param body
     * @param visitor
     */
    public void visitAndUpdateDeclaredViewsBasedOnFindViewById(Block body, MethodBodyVisitor visitor)
    {
        body.accept(visitor);
        synchronized (declaredViewIds)
        {
            declaredViewIds.addAll(visitor.getDeclaredViewIds());
        }

    }

    /**
     * Check if there is an attribute already declared with the name given.
     * @param node
     * @param considerType false, if must not consider the type in the analysis  
     * @return true if there a variable declared with the node.getNodeId() independent on variable type,
     * false otherwise
     */
    public boolean checkIfAttributeAlreadyDeclared(LayoutNode node, boolean considerType)
    {
        boolean containFieldDeclared = false;
        if (typeDeclaration.bodyDeclarations() != null)
        {
            //check if attribute already declared                  
            for (Object bd : typeDeclaration.bodyDeclarations())
            {
                if (bd instanceof FieldDeclaration)
                {
                    FieldDeclaration fd = (FieldDeclaration) bd;
                    if (fd.getParent() instanceof TypeDeclaration)
                    {
                        TypeDeclaration type = (TypeDeclaration) fd.getParent();
                        if (typeDeclaration.equals(type))
                        {
                            //only considers attributes from main class inside the file
                            for (Object fragment : fd.fragments())
                            {
                                if (fragment instanceof VariableDeclarationFragment)
                                {
                                    VariableDeclarationFragment frag =
                                            (VariableDeclarationFragment) fragment;
                                    if ((frag.getName() != null)
                                            && frag.getName().toString().equals(node.getNodeId()))
                                    {
                                        if (considerType)
                                        {
                                            if ((fd.getType() != null)
                                                    && !fd.getType().toString()
                                                            .equals(node.getNodeType()))
                                            {
                                                containFieldDeclared = true;
                                                break;
                                            }
                                        }
                                        else
                                        {
                                            containFieldDeclared = true;
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return containFieldDeclared;
    }

    /**
     * @return the typeAssociatedToLayout
     */
    public CodeGeneratorDataBasedOnLayout.TYPE getTypeAssociatedToLayout()
    {
        return typeAssociatedToLayout;
    }

    /**
     * @return the inflatedViewName
     */
    public String getInflatedViewName()
    {
        return inflatedViewName;
    }

    /**
     * @param inflatedViewName the inflatedViewName to set
     */
    public void setInflatedViewName(String inflatedViewName)
    {
        this.inflatedViewName = inflatedViewName;
    }

    /**
     * @return the list of declared layout ids (as specified in layout.xml under android:id attribute)
     */
    public synchronized Set<String> getDeclaredViewIds()
    {
        return declaredViewIds;
    }

    /**
     * @return the list of view ids that have code to restore state (using SharedPreferences)
     */
    public Set<String> getRestoredViewIds()
    {
        return restoredViewIds;
    }

    /**
     * @return the list of view ids that have code to save state (using SharedPreferences)
     */
    public Set<String> getSavedViewIds()
    {
        return savedViewIds;
    }

}