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