• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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  */
16 
17 package com.android.ide.eclipse.adt.internal.editors.layout.descriptors;
18 
19 import static com.android.sdklib.SdkConstants.CLASS_VIEWGROUP;
20 
21 import com.android.ide.common.resources.platform.ViewClassInfo;
22 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
23 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
24 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
25 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
26 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
27 import com.android.sdklib.IAndroidTarget;
28 
29 import org.eclipse.core.resources.IProject;
30 import org.eclipse.core.runtime.NullProgressMonitor;
31 import org.eclipse.jdt.core.IJavaProject;
32 import org.eclipse.jdt.core.IType;
33 import org.eclipse.jdt.core.ITypeHierarchy;
34 import org.eclipse.jdt.core.JavaCore;
35 import org.eclipse.jdt.core.JavaModelException;
36 import org.eclipse.jdt.ui.ISharedImages;
37 import org.eclipse.jdt.ui.JavaUI;
38 import org.eclipse.jface.resource.ImageDescriptor;
39 import org.eclipse.swt.graphics.Image;
40 
41 import java.util.HashMap;
42 import java.util.List;
43 
44 /**
45  * Service responsible for creating/managing {@link ViewElementDescriptor} objects for custom
46  * View classes per project.
47  * <p/>
48  * The service provides an on-demand monitoring of custom classes to check for changes. Monitoring
49  * starts once a request for an {@link ViewElementDescriptor} object has been done for a specific
50  * class.
51  * <p/>
52  * The monitoring will notify a listener of any changes in the class triggering a change in its
53  * associated {@link ViewElementDescriptor} object.
54  * <p/>
55  * If the custom class does not exist, no monitoring is put in place to avoid having to listen
56  * to all class changes in the projects.
57  */
58 public final class CustomViewDescriptorService {
59 
60     private static CustomViewDescriptorService sThis = new CustomViewDescriptorService();
61 
62     /**
63      * Map where keys are the project, and values are another map containing all the known
64      * custom View class for this project. The custom View class are stored in a map
65      * where the keys are the fully qualified class name, and the values are their associated
66      * {@link ViewElementDescriptor}.
67      */
68     private HashMap<IProject, HashMap<String, ViewElementDescriptor>> mCustomDescriptorMap =
69         new HashMap<IProject, HashMap<String, ViewElementDescriptor>>();
70 
71     /**
72      * TODO will be used to update the ViewElementDescriptor of the custom view when it
73      * is modified (either the class itself or its attributes.xml)
74      */
75     @SuppressWarnings("unused")
76     private ICustomViewDescriptorListener mListener;
77 
78     /**
79      * Classes which implements this interface provide a method that deal with modifications
80      * in custom View class triggering a change in its associated {@link ViewClassInfo} object.
81      */
82     public interface ICustomViewDescriptorListener {
83         /**
84          * Sent when a custom View class has changed and
85          * its {@link ViewElementDescriptor} was modified.
86          *
87          * @param project the project containing the class.
88          * @param className the fully qualified class name.
89          * @param descriptor the updated ElementDescriptor.
90          */
updatedClassInfo(IProject project, String className, ViewElementDescriptor descriptor)91         public void updatedClassInfo(IProject project,
92                                      String className,
93                                      ViewElementDescriptor descriptor);
94     }
95 
96     /**
97      * Returns the singleton instance of {@link CustomViewDescriptorService}.
98      */
getInstance()99     public static CustomViewDescriptorService getInstance() {
100         return sThis;
101     }
102 
103     /**
104      * Sets the listener receiving custom View class modification notifications.
105      * @param listener the listener to receive the notifications.
106      *
107      * TODO will be used to update the ViewElementDescriptor of the custom view when it
108      * is modified (either the class itself or its attributes.xml)
109      */
setListener(ICustomViewDescriptorListener listener)110     public void setListener(ICustomViewDescriptorListener listener) {
111         mListener = listener;
112     }
113 
114     /**
115      * Returns the {@link ViewElementDescriptor} for a particular project/class when the
116      * fully qualified class name actually matches a class from the given project.
117      * <p/>
118      * Custom descriptors are created as needed.
119      * <p/>
120      * If it is the first time the {@link ViewElementDescriptor} is requested, the method
121      * will check that the specified class is in fact a custom View class. Once this is
122      * established, a monitoring for that particular class is initiated. Any change will
123      * trigger a notification to the {@link ICustomViewDescriptorListener}.
124      *
125      * @param project the project containing the class.
126      * @param fqcn the fully qualified name of the class.
127      * @return a {@link ViewElementDescriptor} or <code>null</code> if the class was not
128      *         a custom View class.
129      */
getDescriptor(IProject project, String fqcn)130     public ViewElementDescriptor getDescriptor(IProject project, String fqcn) {
131         // look in the map first
132         synchronized (mCustomDescriptorMap) {
133             HashMap<String, ViewElementDescriptor> map = mCustomDescriptorMap.get(project);
134 
135             if (map != null) {
136                 ViewElementDescriptor descriptor = map.get(fqcn);
137                 if (descriptor != null) {
138                     return descriptor;
139                 }
140             }
141 
142             // if we step here, it looks like we haven't created it yet.
143             // First lets check this is in fact a valid type in the project
144 
145             try {
146                 // We expect the project to be both opened and of java type (since it's an android
147                 // project), so we can create a IJavaProject object from our IProject.
148                 IJavaProject javaProject = JavaCore.create(project);
149 
150                 // replace $ by . in the class name
151                 String javaClassName = fqcn.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
152 
153                 // look for the IType object for this class
154                 IType type = javaProject.findType(javaClassName);
155                 if (type != null && type.exists()) {
156                     // the type exists. Let's get the parent class and its ViewClassInfo.
157 
158                     // get the type hierarchy
159                     ITypeHierarchy hierarchy = type.newSupertypeHierarchy(
160                             new NullProgressMonitor());
161 
162                     ViewElementDescriptor parentDescriptor = createViewDescriptor(
163                             hierarchy.getSuperclass(type), project, hierarchy);
164 
165                     if (parentDescriptor != null) {
166                         // we have a valid parent, lets create a new ViewElementDescriptor.
167 
168                         String name = DescriptorsUtils.getBasename(fqcn);
169                         ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
170                                 getAttributeDescriptor(type, parentDescriptor),
171                                 getLayoutAttributeDescriptors(type, parentDescriptor),
172                                 parentDescriptor.getChildren());
173                         descriptor.setSuperClass(parentDescriptor);
174 
175                         synchronized (mCustomDescriptorMap) {
176                             map = mCustomDescriptorMap.get(project);
177                             if (map == null) {
178                                 map = new HashMap<String, ViewElementDescriptor>();
179                                 mCustomDescriptorMap.put(project, map);
180                             }
181 
182                             map.put(fqcn, descriptor);
183                         }
184 
185                         //TODO setup listener on this resource change.
186 
187                         return descriptor;
188                     }
189                 }
190             } catch (JavaModelException e) {
191                 // there was an error accessing any of the IType, we'll just return null;
192             }
193         }
194 
195         return null;
196     }
197 
198     /**
199      * Computes (if needed) and returns the {@link ViewElementDescriptor} for the specified type.
200      *
201      * @return A {@link ViewElementDescriptor} or null if type or typeHierarchy is null.
202      */
createViewDescriptor(IType type, IProject project, ITypeHierarchy typeHierarchy)203     private ViewElementDescriptor createViewDescriptor(IType type, IProject project,
204             ITypeHierarchy typeHierarchy) {
205         // check if the type is a built-in View class.
206         List<ViewElementDescriptor> builtInList = null;
207 
208         // give up if there's no type
209         if (type == null) {
210             return null;
211         }
212 
213         String fqcn = type.getFullyQualifiedName();
214 
215         Sdk currentSdk = Sdk.getCurrent();
216         if (currentSdk != null) {
217             IAndroidTarget target = currentSdk.getTarget(project);
218             if (target != null) {
219                 AndroidTargetData data = currentSdk.getTargetData(target);
220                 if (data != null) {
221                     LayoutDescriptors descriptors = data.getLayoutDescriptors();
222                     ViewElementDescriptor d = descriptors.findDescriptorByClass(fqcn);
223                     if (d != null) {
224                         return d;
225                     }
226                     builtInList = descriptors.getViewDescriptors();
227                 }
228             }
229         }
230 
231         // it's not a built-in class? Lets look if the superclass is built-in
232         // give up if there's no type
233         if (typeHierarchy == null) {
234             return null;
235         }
236 
237         IType parentType = typeHierarchy.getSuperclass(type);
238         if (parentType != null) {
239             ViewElementDescriptor parentDescriptor = createViewDescriptor(parentType, project,
240                     typeHierarchy);
241 
242             if (parentDescriptor != null) {
243                 // parent class is a valid View class with a descriptor, so we create one
244                 // for this class.
245                 String name = DescriptorsUtils.getBasename(fqcn);
246                 // A custom view accepts children if its parent descriptor also does.
247                 // The only exception to this is ViewGroup, which accepts children even though
248                 // its parent does not.
249                 boolean isViewGroup = fqcn.equals(CLASS_VIEWGROUP);
250                 boolean hasChildren = isViewGroup || parentDescriptor.hasChildren();
251                 ViewElementDescriptor[] children = null;
252                 if (hasChildren && builtInList != null) {
253                     // We can't figure out what the allowable children are by just
254                     // looking at the class, so assume any View is valid
255                     children = builtInList.toArray(new ViewElementDescriptor[builtInList.size()]);
256                 }
257                 ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
258                         getAttributeDescriptor(type, parentDescriptor),
259                         getLayoutAttributeDescriptors(type, parentDescriptor),
260                         children);
261                 descriptor.setSuperClass(parentDescriptor);
262 
263                 // add it to the map
264                 synchronized (mCustomDescriptorMap) {
265                     HashMap<String, ViewElementDescriptor> map = mCustomDescriptorMap.get(project);
266 
267                     if (map == null) {
268                         map = new HashMap<String, ViewElementDescriptor>();
269                         mCustomDescriptorMap.put(project, map);
270                     }
271 
272                     map.put(fqcn, descriptor);
273 
274                 }
275 
276                 //TODO setup listener on this resource change.
277 
278                 return descriptor;
279             }
280         }
281 
282         // class is neither a built-in view class, nor extend one. return null.
283         return null;
284     }
285 
286     /**
287      * Returns the array of {@link AttributeDescriptor} for the specified {@link IType}.
288      * <p/>
289      * The array should contain the descriptor for this type and all its supertypes.
290      *
291      * @param type the type for which the {@link AttributeDescriptor} are returned.
292      * @param parentDescriptor the {@link ViewElementDescriptor} of the direct superclass.
293      */
getAttributeDescriptor(IType type, ViewElementDescriptor parentDescriptor)294     private static AttributeDescriptor[] getAttributeDescriptor(IType type,
295             ViewElementDescriptor parentDescriptor) {
296         // TODO add the class attribute descriptors to the parent descriptors.
297         return parentDescriptor.getAttributes();
298     }
299 
getLayoutAttributeDescriptors(IType type, ViewElementDescriptor parentDescriptor)300     private static AttributeDescriptor[] getLayoutAttributeDescriptors(IType type,
301             ViewElementDescriptor parentDescriptor) {
302         return parentDescriptor.getLayoutAttributes();
303     }
304 
305     private static class CustomViewDescriptor extends ViewElementDescriptor {
CustomViewDescriptor(String name, String fqcn, AttributeDescriptor[] attributes, AttributeDescriptor[] layoutAttributes, ElementDescriptor[] children)306         public CustomViewDescriptor(String name, String fqcn, AttributeDescriptor[] attributes,
307                 AttributeDescriptor[] layoutAttributes,
308                 ElementDescriptor[] children) {
309             super(
310                     fqcn, // xml name
311                     name, // ui name
312                     fqcn, // full class name
313                     fqcn, // tooltip
314                     null, // sdk_url
315                     attributes,
316                     layoutAttributes,
317                     children,
318                     false // mandatory
319             );
320         }
321 
322         @Override
getGenericIcon()323         public Image getGenericIcon() {
324             // Java source file icon. We could use the Java class icon here
325             // (IMG_OBJS_CLASS), but it does not work well on anything but
326             // white backgrounds
327             ISharedImages sharedImages = JavaUI.getSharedImages();
328             String key = ISharedImages.IMG_OBJS_CUNIT;
329             ImageDescriptor descriptor = sharedImages.getImageDescriptor(key);
330             return descriptor.createImage();
331         }
332     }
333 }
334