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 */ 91 public void updatedClassInfo(IProject project, 92 String className, 93 ViewElementDescriptor descriptor); 94 } 95 96 /** 97 * Returns the singleton instance of {@link CustomViewDescriptorService}. 98 */ 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 */ 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 */ 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 */ 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 */ 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 300 private static AttributeDescriptor[] getLayoutAttributeDescriptors(IType type, 301 ViewElementDescriptor parentDescriptor) { 302 return parentDescriptor.getLayoutAttributes(); 303 } 304 305 private static class CustomViewDescriptor extends ViewElementDescriptor { 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 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