aboutsummaryrefslogtreecommitdiffstats
path: root/ide_common/src/com/android/ide/common/resources/ResourceFolder.java
blob: d6464c8078680e52a23cbdc244d4e2e1bead3dfa (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
351
352
353
/*
 * Copyright (C) 2007 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.android.ide.common.resources;

import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.ide.common.resources.configuration.Configurable;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.io.IAbstractFile;
import com.android.io.IAbstractFolder;
import com.android.resources.FolderTypeRelationship;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Resource Folder class. Contains list of {@link ResourceFile}s,
 * the {@link FolderConfiguration}, and a link to the {@link IAbstractFolder} object.
 */
public final class ResourceFolder implements Configurable {
    final ResourceFolderType mType;
    final FolderConfiguration mConfiguration;
    IAbstractFolder mFolder;
    List<ResourceFile> mFiles = null;
    Map<String, ResourceFile> mNames = null;
    private final ResourceRepository mRepository;

    /**
     * Creates a new {@link ResourceFolder}
     * @param type The type of the folder
     * @param config The configuration of the folder
     * @param folder The associated {@link IAbstractFolder} object.
     * @param repository The associated {@link ResourceRepository}
     */
    protected ResourceFolder(ResourceFolderType type, FolderConfiguration config,
            IAbstractFolder folder, ResourceRepository repository) {
        mType = type;
        mConfiguration = config;
        mFolder = folder;
        mRepository = repository;
    }

    /**
     * Processes a file and adds it to its parent folder resource.
     *
     * @param file the underlying resource file.
     * @param kind the file change kind.
     * @param context a context object with state for the current update, such
     *            as a place to stash errors encountered
     * @return the {@link ResourceFile} that was created.
     */
    public ResourceFile processFile(IAbstractFile file, ResourceDeltaKind kind,
            ScanningContext context) {
        // look for this file if it's already been created
        ResourceFile resFile = getFile(file, context);

        if (resFile == null) {
            if (kind != ResourceDeltaKind.REMOVED) {
                // create a ResourceFile for it.

                resFile = createResourceFile(file);
                resFile.load(context);

                // add it to the folder
                addFile(resFile);
            }
        } else {
            if (kind == ResourceDeltaKind.REMOVED) {
                removeFile(resFile, context);
            } else {
                resFile.update(context);
            }
        }

        return resFile;
    }

    private ResourceFile createResourceFile(IAbstractFile file) {
        // check if that's a single or multi resource type folder. For now we define this by
        // the number of possible resource type output by files in the folder.
        // We have a special case for layout/menu folders which can also generate IDs.
        // This does
        // not make the difference between several resource types from a single file or
        // the ability to have 2 files in the same folder generating 2 different types of
        // resource. The former is handled by MultiResourceFile properly while we don't
        // handle the latter. If we were to add this behavior we'd have to change this call.
        List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(mType);

        ResourceFile resFile = null;
        if (types.size() == 1) {
            resFile = new SingleResourceFile(file, this);
        } else if (types.contains(ResourceType.LAYOUT)) {
            resFile = new IdGeneratingResourceFile(file, this, ResourceType.LAYOUT);
        } else if (types.contains(ResourceType.MENU)) {
            resFile = new IdGeneratingResourceFile(file, this, ResourceType.MENU);
        } else {
            resFile = new MultiResourceFile(file, this);
        }
        return resFile;
    }

    /**
     * Adds a {@link ResourceFile} to the folder.
     * @param file The {@link ResourceFile}.
     */
    @VisibleForTesting(visibility=Visibility.PROTECTED)
    public void addFile(ResourceFile file) {
        if (mFiles == null) {
            int initialSize = 16;
            if (mRepository.isFrameworkRepository()) {
                String name = mFolder.getName();
                // Pick some reasonable initial sizes for framework data structures
                // since they are typically (a) large and (b) their sizes are roughly known
                // in advance
                switch (mType) {
                    case DRAWABLE: {
                        // See if it's one of the -mdpi, -hdpi etc folders which
                        // are large (~1250 items)
                        int index = name.indexOf('-');
                        if (index == -1) {
                            initialSize = 230; // "drawable" folder
                        } else {
                            index = name.indexOf('-', index + 1);
                            if (index == -1) {
                                // One of the "drawable-<density>" folders
                                initialSize = 1260;
                            } else {
                                // "drawable-sw600dp-hdpi" etc
                                initialSize = 30;
                            }
                        }
                        break;
                    }
                    case LAYOUT: {
                        // The main layout folder has about ~185 layouts in it;
                        // the others are small
                        if (name.indexOf('-') == -1) {
                            initialSize = 200;
                        }
                        break;
                    }
                    case VALUES: {
                        if (name.indexOf('-') == -1) {
                            initialSize = 32;
                        } else {
                            initialSize = 4;
                        }
                        break;
                    }
                    case ANIM: initialSize = 85; break;
                    case COLOR: initialSize = 32; break;
                    case RAW: initialSize = 4; break;
                    default:
                        // Stick with the 16 default
                        break;
                }
            }

            mFiles = new ArrayList<ResourceFile>(initialSize);
            mNames = new HashMap<String, ResourceFile>(initialSize, 2.0f);
        }

        mFiles.add(file);
        mNames.put(file.getFile().getName(), file);
    }

    protected void removeFile(ResourceFile file, ScanningContext context) {
        file.dispose(context);
        mFiles.remove(file);
        mNames.remove(file.getFile().getName());
    }

    protected void dispose(ScanningContext context) {
        if (mFiles != null) {
            for (ResourceFile file : mFiles) {
                file.dispose(context);
            }

            mFiles.clear();
            mNames.clear();
        }
    }

    /**
     * Returns the {@link IAbstractFolder} associated with this object.
     */
    public IAbstractFolder getFolder() {
        return mFolder;
    }

    /**
     * Returns the {@link ResourceFolderType} of this object.
     */
    public ResourceFolderType getType() {
        return mType;
    }

    public ResourceRepository getRepository() {
        return mRepository;
    }

    /**
     * Returns the list of {@link ResourceType}s generated by the files inside this folder.
     */
    public Collection<ResourceType> getResourceTypes() {
        ArrayList<ResourceType> list = new ArrayList<ResourceType>();

        if (mFiles != null) {
            for (ResourceFile file : mFiles) {
                Collection<ResourceType> types = file.getResourceTypes();

                // loop through those and add them to the main list,
                // if they are not already present
                for (ResourceType resType : types) {
                    if (list.indexOf(resType) == -1) {
                        list.add(resType);
                    }
                }
            }
        }

        return list;
    }

    @Override
    public FolderConfiguration getConfiguration() {
        return mConfiguration;
    }

    /**
     * Returns whether the folder contains a file with the given name.
     * @param name the name of the file.
     */
    public boolean hasFile(String name) {
        if (mNames != null && mNames.containsKey(name)) {
            return true;
        }

        // Note: mNames.containsKey(name) is faster, but doesn't give the same result; this
        // method seems to be called on this ResourceFolder before it has been processed,
        // so we need to use the file system check instead:
        return mFolder.hasFile(name);
    }

    /**
     * Returns the {@link ResourceFile} matching a {@link IAbstractFile} object.
     *
     * @param file The {@link IAbstractFile} object.
     * @param context a context object with state for the current update, such
     *            as a place to stash errors encountered
     * @return the {@link ResourceFile} or null if no match was found.
     */
    private ResourceFile getFile(IAbstractFile file, ScanningContext context) {
        assert mFolder.equals(file.getParentFolder());

        if (mNames != null) {
            ResourceFile resFile = mNames.get(file.getName());
            if (resFile != null) {
                return resFile;
            }
        }

        // If the file actually exists, the resource folder  may not have been
        // scanned yet; add it lazily
        if (file.exists()) {
            ResourceFile resFile = createResourceFile(file);
            resFile.load(context);
            addFile(resFile);
            return resFile;
        }

        return null;
    }

    /**
     * Returns the {@link ResourceFile} matching a given name.
     * @param filename The name of the file to return.
     * @return the {@link ResourceFile} or <code>null</code> if no match was found.
     */
    public ResourceFile getFile(String filename) {
        if (mNames != null) {
            ResourceFile resFile = mNames.get(filename);
            if (resFile != null) {
                return resFile;
            }
        }

        // If the file actually exists, the resource folder  may not have been
        // scanned yet; add it lazily
        IAbstractFile file = mFolder.getFile(filename);
        if (file != null && file.exists()) {
            ResourceFile resFile = createResourceFile(file);
            resFile.load(new ScanningContext(mRepository));
            addFile(resFile);
            return resFile;
        }

        return null;
    }

    /**
     * Returns whether a file in the folder is generating a resource of a specified type.
     * @param type The {@link ResourceType} being looked up.
     */
    public boolean hasResources(ResourceType type) {
        // Check if the folder type is able to generate resource of the type that was asked.
        // this is a first check to avoid going through the files.
        List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type);

        boolean valid = false;
        for (ResourceFolderType rft : folderTypes) {
            if (rft == mType) {
                valid = true;
                break;
            }
        }

        if (valid) {
            if (mFiles != null) {
                for (ResourceFile f : mFiles) {
                    if (f.hasResources(type)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public String toString() {
        return mFolder.toString();
    }
}