1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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 */
16package com.android.ide.eclipse.adt.internal.editors.layout.properties;
17
18import static com.android.SdkConstants.ANDROID_PKG;
19import static com.android.SdkConstants.ANDROID_PREFIX;
20import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
21import static com.android.SdkConstants.NEW_ID_PREFIX;
22import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
23import static com.android.SdkConstants.PREFIX_THEME_REF;
24
25import com.android.ide.common.resources.ResourceItem;
26import com.android.ide.common.resources.ResourceRepository;
27import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
28import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
29import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
30import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode;
31import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
32import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
33import com.android.resources.ResourceType;
34import com.android.utils.SdkUtils;
35
36import org.eclipse.core.resources.IProject;
37import org.eclipse.jface.fieldassist.ContentProposal;
38import org.eclipse.jface.fieldassist.IContentProposal;
39import org.eclipse.jface.fieldassist.IContentProposalProvider;
40
41import java.util.ArrayList;
42import java.util.Collections;
43import java.util.List;
44
45/**
46 * Resource value completion for the given property
47 * <p>
48 * TODO:
49 * <ul>
50 * <li>also offer other values seen in the app
51 * <li>also offer previously set values for this property
52 * <li>also complete on properties
53 * </ul>
54 */
55class ResourceValueCompleter implements IContentProposalProvider {
56    protected final XmlProperty xmlProperty;
57
58    ResourceValueCompleter(XmlProperty xmlProperty) {
59        this.xmlProperty = xmlProperty;
60    }
61
62    @Override
63    public IContentProposal[] getProposals(String contents, int position) {
64        if (contents.startsWith(PREFIX_RESOURCE_REF)) {
65            CommonXmlEditor editor = this.xmlProperty.getXmlEditor();
66            if (editor != null) {
67                String[] matches = computeResourceStringMatches(
68                        editor,
69                        this.xmlProperty.mDescriptor, contents.substring(0, position));
70                List<IContentProposal> proposals = null;
71                if (matches != null && matches.length > 0) {
72                    proposals = new ArrayList<IContentProposal>(matches.length);
73                    for (String match : matches) {
74                        proposals.add(new ContentProposal(match));
75                    }
76                    return proposals.toArray(new IContentProposal[proposals.size()]);
77                }
78            }
79        }
80
81        return new IContentProposal[0];
82    }
83
84    /**
85     * Similar to {@link UiResourceAttributeNode#computeResourceStringMatches}
86     * but computes complete results up front rather than dividing it up into
87     * smaller chunks like @{code @android:}, {@code string/}, and {@code ok}.
88     */
89    static String[] computeResourceStringMatches(AndroidXmlEditor editor,
90            AttributeDescriptor attributeDescriptor, String prefix) {
91        List<String> results = new ArrayList<String>(200);
92
93        // System matches: only do this if the value already matches at least @a,
94        // and doesn't start with something that can't possibly be @android
95        if (prefix.startsWith("@a") && //$NON-NLS-1$
96                prefix.regionMatches(true /* ignoreCase */, 0, ANDROID_PREFIX, 0,
97                        Math.min(prefix.length() - 1, ANDROID_PREFIX.length()))) {
98            AndroidTargetData data = editor.getTargetData();
99            if (data != null) {
100                ResourceRepository repository = data.getFrameworkResources();
101                addMatches(repository, prefix, true /* isSystem */, results);
102            }
103        } else if (prefix.startsWith("?") && //$NON-NLS-1$
104                prefix.regionMatches(true /* ignoreCase */, 0, ANDROID_THEME_PREFIX, 0,
105                        Math.min(prefix.length() - 1, ANDROID_THEME_PREFIX.length()))) {
106            AndroidTargetData data = editor.getTargetData();
107            if (data != null) {
108                ResourceRepository repository = data.getFrameworkResources();
109                addMatches(repository, prefix, true /* isSystem */, results);
110            }
111        }
112
113
114        // When completing project resources skip framework resources unless
115        // the prefix possibly completes both, such as "@an" which can match
116        // both the project resource @animator as well as @android:string
117        if (!prefix.startsWith("@and") && !prefix.startsWith("?and")) { //$NON-NLS-1$ //$NON-NLS-2$
118            IProject project = editor.getProject();
119            if (project != null) {
120                // get the resource repository for this project and the system resources.
121                ResourceManager manager = ResourceManager.getInstance();
122                ResourceRepository repository = manager.getProjectResources(project);
123                if (repository != null) {
124                    // We have a style name and a repository. Find all resources that match this
125                    // type and recreate suggestions out of them.
126                    addMatches(repository, prefix, false /* isSystem */, results);
127                }
128
129            }
130        }
131
132        if (attributeDescriptor != null) {
133            UiResourceAttributeNode.sortAttributeChoices(attributeDescriptor, results);
134        } else {
135            Collections.sort(results);
136        }
137
138        return results.toArray(new String[results.size()]);
139    }
140
141    private static void addMatches(ResourceRepository repository, String prefix, boolean isSystem,
142            List<String> results) {
143        int typeStart = isSystem
144                ? ANDROID_PREFIX.length() : PREFIX_RESOURCE_REF.length();
145
146        for (ResourceType type : repository.getAvailableResourceTypes()) {
147            if (prefix.regionMatches(typeStart, type.getName(), 0,
148                    Math.min(type.getName().length(), prefix.length() - typeStart))) {
149                StringBuilder sb = new StringBuilder();
150                if (prefix.length() == 0 || prefix.startsWith(PREFIX_RESOURCE_REF)) {
151                    sb.append(PREFIX_RESOURCE_REF);
152                } else {
153                    if (type != ResourceType.ATTR) {
154                        continue;
155                    }
156                    sb.append(PREFIX_THEME_REF);
157                }
158
159                if (type == ResourceType.ID && prefix.startsWith(NEW_ID_PREFIX)) {
160                    sb.append('+');
161                }
162
163                if (isSystem) {
164                    sb.append(ANDROID_PKG).append(':');
165                }
166
167                sb.append(type.getName()).append('/');
168                String base = sb.toString();
169
170                int nameStart = typeStart + type.getName().length() + 1; // +1: add "/" divider
171                String namePrefix =
172                        prefix.length() <= nameStart ? "" : prefix.substring(nameStart);
173                for (ResourceItem item : repository.getResourceItemsOfType(type)) {
174                    String name = item.getName();
175                    if (SdkUtils.startsWithIgnoreCase(name, namePrefix)) {
176                        results.add(base + name);
177                    }
178                }
179            }
180        }
181    }
182}
183