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.editors.layout.gscripts.IAttributeInfo.Format; 20 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; 21 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; 22 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; 23 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; 24 import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; 25 import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor; 26 import com.android.ide.eclipse.adt.internal.resources.AttributeInfo; 27 import com.android.ide.eclipse.adt.internal.resources.ViewClassInfo; 28 import com.android.ide.eclipse.adt.internal.resources.ViewClassInfo.LayoutParamsInfo; 29 import com.android.sdklib.SdkConstants; 30 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map.Entry; 36 37 38 /** 39 * Complete description of the layout structure. 40 */ 41 public final class LayoutDescriptors implements IDescriptorProvider { 42 43 // Public attributes names, attributes descriptors and elements descriptors 44 public static final String ID_ATTR = "id"; //$NON-NLS-1$ 45 46 /** The document descriptor. Contains all layouts and views linked together. */ 47 private DocumentDescriptor mRootDescriptor = 48 new DocumentDescriptor("layout_doc", null); //$NON-NLS-1$ 49 50 /** The list of all known ViewLayout descriptors. */ 51 private ArrayList<ElementDescriptor> mLayoutDescriptors = new ArrayList<ElementDescriptor>(); 52 53 /** Read-Only list of View Descriptors. */ 54 private List<ElementDescriptor> mROLayoutDescriptors; 55 56 /** The list of all known View (not ViewLayout) descriptors. */ 57 private ArrayList<ElementDescriptor> mViewDescriptors = new ArrayList<ElementDescriptor>(); 58 59 /** Read-Only list of View Descriptors. */ 60 private List<ElementDescriptor> mROViewDescriptors; 61 62 /** The descriptor matching android.view.View. */ 63 private ElementDescriptor mBaseViewDescriptor; 64 65 /** Returns the document descriptor. Contains all layouts and views linked together. */ getDescriptor()66 public DocumentDescriptor getDescriptor() { 67 return mRootDescriptor; 68 } 69 70 /** Returns the read-only list of all known ViewLayout descriptors. */ getLayoutDescriptors()71 public List<ElementDescriptor> getLayoutDescriptors() { 72 return mROLayoutDescriptors; 73 } 74 75 /** Returns the read-only list of all known View (not ViewLayout) descriptors. */ getViewDescriptors()76 public List<ElementDescriptor> getViewDescriptors() { 77 return mROViewDescriptors; 78 } 79 getRootElementDescriptors()80 public ElementDescriptor[] getRootElementDescriptors() { 81 return mRootDescriptor.getChildren(); 82 } 83 84 /** 85 * Returns the descriptor matching android.view.View. 86 */ getBaseViewDescriptor()87 public ElementDescriptor getBaseViewDescriptor() { 88 if (mBaseViewDescriptor == null) { 89 for (ElementDescriptor desc : mViewDescriptors) { 90 if (desc instanceof ViewElementDescriptor) { 91 ViewElementDescriptor viewDesc = (ViewElementDescriptor) desc; 92 if (SdkConstants.CLASS_VIEW.equals(viewDesc.getFullClassName())) { 93 mBaseViewDescriptor = viewDesc; 94 break; 95 } 96 } 97 98 } 99 } 100 return mBaseViewDescriptor; 101 } 102 103 /** 104 * Updates the document descriptor. 105 * <p/> 106 * It first computes the new children of the descriptor and then update them 107 * all at once. 108 * <p/> 109 * TODO: differentiate groups from views in the tree UI? => rely on icons 110 * <p/> 111 * 112 * @param views The list of views in the framework. 113 * @param layouts The list of layouts in the framework. 114 */ updateDescriptors(ViewClassInfo[] views, ViewClassInfo[] layouts)115 public synchronized void updateDescriptors(ViewClassInfo[] views, ViewClassInfo[] layouts) { 116 117 // This map links every ViewClassInfo to the ElementDescriptor we created. 118 // It is filled by convertView() and used later to fix the super-class hierarchy. 119 HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap = 120 new HashMap<ViewClassInfo, ViewElementDescriptor>(); 121 122 ArrayList<ElementDescriptor> newViews = new ArrayList<ElementDescriptor>(); 123 if (views != null) { 124 for (ViewClassInfo info : views) { 125 ElementDescriptor desc = convertView(info, infoDescMap); 126 newViews.add(desc); 127 } 128 } 129 130 // Create <include> as a synthetic regular view. 131 // Note: ViewStub is already described by attrs.xml 132 insertInclude(newViews); 133 134 ArrayList<ElementDescriptor> newLayouts = new ArrayList<ElementDescriptor>(); 135 if (layouts != null) { 136 for (ViewClassInfo info : layouts) { 137 ElementDescriptor desc = convertView(info, infoDescMap); 138 newLayouts.add(desc); 139 } 140 } 141 142 ArrayList<ElementDescriptor> newDescriptors = new ArrayList<ElementDescriptor>(); 143 newDescriptors.addAll(newLayouts); 144 newDescriptors.addAll(newViews); 145 146 // Link all layouts to everything else here.. recursively 147 for (ElementDescriptor layoutDesc : newLayouts) { 148 layoutDesc.setChildren(newDescriptors); 149 } 150 151 fixSuperClasses(infoDescMap); 152 153 // The <merge> tag can only be a root tag, so it is added at the end. 154 // It gets everything else as children but it is not made a child itself. 155 ElementDescriptor mergeTag = createMerge(newLayouts); 156 mergeTag.setChildren(newDescriptors); // mergeTag makes a copy of the list 157 newDescriptors.add(mergeTag); 158 newLayouts.add(mergeTag); 159 160 mViewDescriptors = newViews; 161 mLayoutDescriptors = newLayouts; 162 mRootDescriptor.setChildren(newDescriptors); 163 164 mBaseViewDescriptor = null; 165 mROLayoutDescriptors = Collections.unmodifiableList(mLayoutDescriptors); 166 mROViewDescriptors = Collections.unmodifiableList(mViewDescriptors); 167 } 168 169 /** 170 * Creates an element descriptor from a given {@link ViewClassInfo}. 171 * 172 * @param info The {@link ViewClassInfo} to convert into a new {@link ViewElementDescriptor}. 173 * @param infoDescMap This map links every ViewClassInfo to the ElementDescriptor it created. 174 * It is filled by here and used later to fix the super-class hierarchy. 175 */ convertView( ViewClassInfo info, HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap)176 private ElementDescriptor convertView( 177 ViewClassInfo info, 178 HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap) { 179 String xml_name = info.getShortClassName(); 180 String tooltip = info.getJavaDoc(); 181 182 ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>(); 183 184 // All views and groups have an implicit "style" attribute which is a reference. 185 AttributeInfo styleInfo = new AttributeInfo( 186 "style", //$NON-NLS-1$ xmlLocalName 187 new Format[] { Format.REFERENCE }); 188 styleInfo.setJavaDoc("A reference to a custom style"); //tooltip 189 DescriptorsUtils.appendAttribute(attributes, 190 "style", //$NON-NLS-1$ 191 null, //nsUri 192 styleInfo, 193 false, //required 194 null); // overrides 195 196 // Process all View attributes 197 DescriptorsUtils.appendAttributes(attributes, 198 null, // elementName 199 SdkConstants.NS_RESOURCES, 200 info.getAttributes(), 201 null, // requiredAttributes 202 null /* overrides */); 203 204 for (ViewClassInfo link = info.getSuperClass(); 205 link != null; 206 link = link.getSuperClass()) { 207 AttributeInfo[] attrList = link.getAttributes(); 208 if (attrList.length > 0) { 209 attributes.add(new SeparatorAttributeDescriptor( 210 String.format("Attributes from %1$s", link.getShortClassName()))); 211 DescriptorsUtils.appendAttributes(attributes, 212 null, // elementName 213 SdkConstants.NS_RESOURCES, 214 attrList, 215 null, // requiredAttributes 216 null /* overrides */); 217 } 218 } 219 220 // Process all LayoutParams attributes 221 ArrayList<AttributeDescriptor> layoutAttributes = new ArrayList<AttributeDescriptor>(); 222 LayoutParamsInfo layoutParams = info.getLayoutData(); 223 224 for(; layoutParams != null; layoutParams = layoutParams.getSuperClass()) { 225 boolean need_separator = true; 226 for (AttributeInfo attr_info : layoutParams.getAttributes()) { 227 if (DescriptorsUtils.containsAttribute(layoutAttributes, 228 SdkConstants.NS_RESOURCES, attr_info)) { 229 continue; 230 } 231 if (need_separator) { 232 String title; 233 if (layoutParams.getShortClassName().equals( 234 SdkConstants.CLASS_NAME_LAYOUTPARAMS)) { 235 title = String.format("Layout Attributes from %1$s", 236 layoutParams.getViewLayoutClass().getShortClassName()); 237 } else { 238 title = String.format("Layout Attributes from %1$s (%2$s)", 239 layoutParams.getViewLayoutClass().getShortClassName(), 240 layoutParams.getShortClassName()); 241 } 242 layoutAttributes.add(new SeparatorAttributeDescriptor(title)); 243 need_separator = false; 244 } 245 DescriptorsUtils.appendAttribute(layoutAttributes, 246 null, // elementName 247 SdkConstants.NS_RESOURCES, 248 attr_info, 249 false, // required 250 null /* overrides */); 251 } 252 } 253 254 ViewElementDescriptor desc = new ViewElementDescriptor(xml_name, 255 xml_name, // ui_name 256 info.getFullClassName(), 257 tooltip, 258 null, // sdk_url 259 attributes.toArray(new AttributeDescriptor[attributes.size()]), 260 layoutAttributes.toArray(new AttributeDescriptor[layoutAttributes.size()]), 261 null, // children 262 false /* mandatory */); 263 infoDescMap.put(info, desc); 264 return desc; 265 } 266 267 /** 268 * Creates a new <include> descriptor and adds it to the list of view descriptors. 269 * 270 * @param knownViews A list of view descriptors being populated. Also used to find the 271 * View descriptor and extract its layout attributes. 272 */ insertInclude(ArrayList<ElementDescriptor> knownViews)273 private void insertInclude(ArrayList<ElementDescriptor> knownViews) { 274 String xml_name = "include"; //$NON-NLS-1$ 275 276 // Create the include custom attributes 277 ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>(); 278 279 // Note that the "layout" attribute does NOT have the Android namespace 280 DescriptorsUtils.appendAttribute(attributes, 281 null, //elementXmlName 282 null, //nsUri 283 new AttributeInfo( 284 "layout", //$NON-NLS-1$ 285 new Format[] { Format.REFERENCE } ), 286 true, //required 287 null); //overrides 288 289 DescriptorsUtils.appendAttribute(attributes, 290 null, //elementXmlName 291 SdkConstants.NS_RESOURCES, //nsUri 292 new AttributeInfo( 293 "id", //$NON-NLS-1$ 294 new Format[] { Format.REFERENCE } ), 295 true, //required 296 null); //overrides 297 298 // Find View and inherit all its layout attributes 299 AttributeDescriptor[] viewLayoutAttribs = findViewLayoutAttributes( 300 SdkConstants.CLASS_VIEW, knownViews); 301 302 // Create the include descriptor 303 ViewElementDescriptor desc = new ViewElementDescriptor(xml_name, // xml_name 304 xml_name, // ui_name 305 null, // canonical class name, we don't have one 306 "Lets you statically include XML layouts inside other XML layouts.", // tooltip 307 null, // sdk_url 308 attributes.toArray(new AttributeDescriptor[attributes.size()]), 309 viewLayoutAttribs, // layout attributes 310 null, // children 311 false /* mandatory */); 312 313 knownViews.add(desc); 314 } 315 316 /** 317 * Creates and return a new <merge> descriptor. 318 * @param knownLayouts A list of all known layout view descriptors, used to find the 319 * FrameLayout descriptor and extract its layout attributes. 320 */ createMerge(ArrayList<ElementDescriptor> knownLayouts)321 private ElementDescriptor createMerge(ArrayList<ElementDescriptor> knownLayouts) { 322 String xml_name = "merge"; //$NON-NLS-1$ 323 324 // Find View and inherit all its layout attributes 325 AttributeDescriptor[] viewLayoutAttribs = findViewLayoutAttributes( 326 SdkConstants.CLASS_FRAMELAYOUT, knownLayouts); 327 328 // Create the include descriptor 329 ViewElementDescriptor desc = new ViewElementDescriptor(xml_name, // xml_name 330 xml_name, // ui_name 331 null, // canonical class name, we don't have one 332 "A root tag useful for XML layouts inflated using a ViewStub.", // tooltip 333 null, // sdk_url 334 null, // attributes 335 viewLayoutAttribs, // layout attributes 336 null, // children 337 false /* mandatory */); 338 339 return desc; 340 } 341 342 /** 343 * Finds the descriptor and retrieves all its layout attributes. 344 */ findViewLayoutAttributes( String viewFqcn, ArrayList<ElementDescriptor> knownViews)345 private AttributeDescriptor[] findViewLayoutAttributes( 346 String viewFqcn, 347 ArrayList<ElementDescriptor> knownViews) { 348 349 for (ElementDescriptor desc : knownViews) { 350 if (desc instanceof ViewElementDescriptor) { 351 ViewElementDescriptor viewDesc = (ViewElementDescriptor) desc; 352 if (viewFqcn.equals(viewDesc.getFullClassName())) { 353 return viewDesc.getLayoutAttributes(); 354 } 355 } 356 } 357 358 return null; 359 } 360 361 /** 362 * Set the super-class of each {@link ViewElementDescriptor} by using the super-class 363 * information available in the {@link ViewClassInfo}. 364 */ fixSuperClasses(HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap)365 private void fixSuperClasses(HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap) { 366 367 for (Entry<ViewClassInfo, ViewElementDescriptor> entry : infoDescMap.entrySet()) { 368 ViewClassInfo info = entry.getKey(); 369 ViewElementDescriptor desc = entry.getValue(); 370 371 ViewClassInfo sup = info.getSuperClass(); 372 if (sup != null) { 373 ViewElementDescriptor supDesc = infoDescMap.get(sup); 374 while (supDesc == null && sup != null) { 375 // We don't have a descriptor for the super-class. That means the class is 376 // probably abstract, so we just need to walk up the super-class chain till 377 // we find one we have. All views derive from android.view.View so we should 378 // surely find that eventually. 379 sup = sup.getSuperClass(); 380 if (sup != null) { 381 supDesc = infoDescMap.get(sup); 382 } 383 } 384 if (supDesc != null) { 385 desc.setSuperClass(supDesc); 386 } 387 } 388 } 389 } 390 } 391