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