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