/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.editors.layout.descriptors;
import com.android.ide.common.api.IAttributeInfo.Format;
import com.android.ide.common.resources.platform.AttributeInfo;
import com.android.ide.common.resources.platform.ViewClassInfo;
import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor;
import com.android.sdklib.SdkConstants;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Complete description of the layout structure.
*/
public final class LayoutDescriptors implements IDescriptorProvider {
/**
* The XML name of the special <include> layout tag.
* A synthetic element with that name is created as part of the view descriptors list
* returned by {@link #getViewDescriptors()}.
*/
public static final String VIEW_INCLUDE = "include"; //$NON-NLS-1$
/**
* The XML name of the special <merge> layout tag.
* A synthetic element with that name is created as part of the view descriptors list
* returned by {@link #getViewDescriptors()}.
*/
public static final String VIEW_MERGE = "merge"; //$NON-NLS-1$
/**
* The attribute name of the include tag's url naming the resource to be inserted
* <p>
* <b>NOTE</b>: The layout attribute is NOT in the Android namespace!
*/
public static final String ATTR_LAYOUT = "layout"; //$NON-NLS-1$
// Public attributes names, attributes descriptors and elements descriptors
public static final String ID_ATTR = "id"; //$NON-NLS-1$
/** The document descriptor. Contains all layouts and views linked together. */
private DocumentDescriptor mRootDescriptor =
new DocumentDescriptor("layout_doc", null); //$NON-NLS-1$
/** The list of all known ViewLayout descriptors. */
private List<ViewElementDescriptor> mLayoutDescriptors =
new ArrayList<ViewElementDescriptor>();
/** Read-Only list of View Descriptors. */
private List<ViewElementDescriptor> mROLayoutDescriptors;
/** The list of all known View (not ViewLayout) descriptors. */
private List<ViewElementDescriptor> mViewDescriptors = new ArrayList<ViewElementDescriptor>();
/** Read-Only list of View Descriptors. */
private List<ViewElementDescriptor> mROViewDescriptors;
/** The descriptor matching android.view.View. */
private ViewElementDescriptor mBaseViewDescriptor;
/** Returns the document descriptor. Contains all layouts and views linked together. */
public DocumentDescriptor getDescriptor() {
return mRootDescriptor;
}
/** Returns the read-only list of all known ViewLayout descriptors. */
public List<ViewElementDescriptor> getLayoutDescriptors() {
return mROLayoutDescriptors;
}
/** Returns the read-only list of all known View (not ViewLayout) descriptors. */
public List<ViewElementDescriptor> getViewDescriptors() {
return mROViewDescriptors;
}
public ElementDescriptor[] getRootElementDescriptors() {
return mRootDescriptor.getChildren();
}
/**
* Returns the descriptor matching android.view.View, which is guaranteed
* to be a {@link ViewElementDescriptor}.
*/
public ViewElementDescriptor getBaseViewDescriptor() {
if (mBaseViewDescriptor == null) {
for (ElementDescriptor desc : mViewDescriptors) {
if (desc instanceof ViewElementDescriptor) {
ViewElementDescriptor viewDesc = (ViewElementDescriptor) desc;
if (SdkConstants.CLASS_VIEW.equals(viewDesc.getFullClassName())) {
mBaseViewDescriptor = viewDesc;
break;
}
}
}
}
return mBaseViewDescriptor;
}
/**
* Updates the document descriptor.
* <p/>
* It first computes the new children of the descriptor and then update them
* all at once.
* <p/>
* TODO: differentiate groups from views in the tree UI? => rely on icons
* <p/>
*
* @param views The list of views in the framework.
* @param layouts The list of layouts in the framework.
*/
public synchronized void updateDescriptors(ViewClassInfo[] views, ViewClassInfo[] layouts) {
// This map links every ViewClassInfo to the ElementDescriptor we created.
// It is filled by convertView() and used later to fix the super-class hierarchy.
HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap =
new HashMap<ViewClassInfo, ViewElementDescriptor>();
ArrayList<ViewElementDescriptor> newViews = new ArrayList<ViewElementDescriptor>();
if (views != null) {
for (ViewClassInfo info : views) {
ViewElementDescriptor desc = convertView(info, infoDescMap);
newViews.add(desc);
}
}
// Create <include> as a synthetic regular view.
// Note: ViewStub is already described by attrs.xml
insertInclude(newViews);
List<ViewElementDescriptor> newLayouts = new ArrayList<ViewElementDescriptor>();
if (layouts != null) {
for (ViewClassInfo info : layouts) {
ViewElementDescriptor desc = convertView(info, infoDescMap);
newLayouts.add(desc);
}
}
List<ElementDescriptor> newDescriptors = new ArrayList<ElementDescriptor>();
newDescriptors.addAll(newLayouts);
newDescriptors.addAll(newViews);
// Link all layouts to everything else here.. recursively
for (ViewElementDescriptor layoutDesc : newLayouts) {
layoutDesc.setChildren(newDescriptors);
}
fixSuperClasses(infoDescMap);
// The <merge> tag can only be a root tag, so it is added at the end.
// It gets everything else as children but it is not made a child itself.
ViewElementDescriptor mergeTag = createMerge(newLayouts);
mergeTag.setChildren(newDescriptors); // mergeTag makes a copy of the list
newDescriptors.add(mergeTag);
newLayouts.add(mergeTag);
// Sort palette contents
Collections.sort(newViews);
Collections.sort(newLayouts);
mViewDescriptors = newViews;
mLayoutDescriptors = newLayouts;
mRootDescriptor.setChildren(newDescriptors);
mBaseViewDescriptor = null;
mROLayoutDescriptors = Collections.unmodifiableList(mLayoutDescriptors);
mROViewDescriptors = Collections.unmodifiableList(mViewDescriptors);
}
/**
* Creates an element descriptor from a given {@link ViewClassInfo}.
*
* @param info The {@link ViewClassInfo} to convert into a new {@link ViewElementDescriptor}.
* @param infoDescMap This map links every ViewClassInfo to the ElementDescriptor it created.
* It is filled by here and used later to fix the super-class hierarchy.
*/
private ViewElementDescriptor convertView(
ViewClassInfo info,
HashMap<ViewClassInfo, ViewElementDescriptor> infoDescMap) {
String xml_name = info.getShortClassName();
String tooltip = info.getJavaDoc();
ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>();
// All views and groups have an implicit "style" attribute which is a reference.
AttributeInfo styleInfo = new AttributeInfo(
"style", //$NON-NLS-1$ xmlLocalName
new Format[] { Format.REFERENCE });
styleInfo.setJavaDoc("A reference to a custom style"); //tooltip
DescriptorsUtils.appendAttribute(attributes,
"style", //$NON-NLS-1$
null, //nsUri
styleInfo,
false, //required
null); // overrides
// Process all View attributes
DescriptorsUtils.appendAttributes(attributes,
null, // elementName
SdkConstants.NS_RESOURCES,
info.getAttributes(),
null, // requiredAttributes
null /* overrides */);
for (ViewClassInfo link = info.getSuperClass();
link != null;
link = link.getSuperClass()) {
AttributeInfo[] attrList = link.getAttributes();
if (attrList.length > 0) {
attributes.add(new SeparatorAttributeDescriptor(
String.format("Attributes from %1$s", link.getShortClassName())));
DescriptorsUtils.appendAttributes(attributes,
null, // elementName
SdkConstants.NS_RESOURCES,
attrList,
null, // requiredAttributes
null /* overrides */);
}
}
// Process all LayoutParams attributes
ArrayList<AttributeDescriptor> layoutAttributes = new ArrayList<AttributeDescriptor>();
LayoutParamsInfo layoutParams = info.getLayoutData();
for(; layoutParams != null; layoutParams = layoutParams.getSuperClass()) {
boolean need_separator = true;
for (AttributeInfo attr_info : layoutParams.getAttributes()) {
if (DescriptorsUtils.containsAttribute(layoutAttributes,
SdkConstants.NS_RESOURCES, attr_info)) {
continue;
}
if (need_separator) {
String title;
if (layoutParams.getShortClassName().equals(
SdkConstants.CLASS_NAME_LAYOUTPARAMS)) {
title = String.format("Layout Attributes from %1$s",
layoutParams.getViewLayoutClass().getShortClassName());
} else {
title = String.format("Layout Attributes from %1$s (%2$s)",
layoutParams.getViewLayoutClass().getShortClassName(),
layoutParams.getShortClassName());
}
layoutAttributes.add(new SeparatorAttributeDescriptor(title));
need_separator = false;
}
DescriptorsUtils.appendAttribute(layoutAttributes,
null, // elementName
SdkConstants.NS_RESOURCES,
attr_info,
false, // required
null /* overrides */);
}
}
ViewElementDescriptor desc = new ViewElementDescriptor(xml_name,
xml_name, // ui_name
info.getFullClassName(),
tooltip,
null, // sdk_url
attributes.toArray(new AttributeDescriptor[attributes.size()]),
layoutAttributes.toArray(new AttributeDescriptor[layoutAttributes.size()]),
null, // children
false /* mandatory */);
infoDescMap.put(info, desc);
return desc;
}
/**
* Creates a new <include> descriptor and adds it to the list of view descriptors.
*
* @param knownViews A list of view descriptors being populated. Also used to find the
* View descriptor and extract its layout attributes.
*/
private void insertInclude(List<ViewElementDescriptor> knownViews) {
String xml_name = VIEW_INCLUDE;
// Create the include custom attributes
ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>();
// Note that the "layout" attribute does NOT have the Android namespace
DescriptorsUtils.appendAttribute(attributes,
null, //elementXmlName
null, //nsUri
new AttributeInfo(
ATTR_LAYOUT,
new Format[] { Format.REFERENCE } ),
true, //required
null); //overrides
DescriptorsUtils.appendAttribute(attributes,
null, //elementXmlName
SdkConstants.NS_RESOURCES, //nsUri
new AttributeInfo(
"id", //$NON-NLS-1$
new Format[] { Format.REFERENCE } ),
true, //required
null); //overrides
// Find View and inherit all its layout attributes
AttributeDescriptor[] viewLayoutAttribs = findViewLayoutAttributes(
SdkConstants.CLASS_VIEW, knownViews);
// Create the include descriptor
ViewElementDescriptor desc = new ViewElementDescriptor(xml_name, // xml_name
xml_name, // ui_name
VIEW_INCLUDE, // "class name"; the GLE only treats this as an element tag
"Lets you statically include XML layouts inside other XML layouts.", // tooltip
null, // sdk_url
attributes.toArray(new AttributeDescriptor[attributes.size()]),
viewLayoutAttribs, // layout attributes
null, // children
false /* mandatory */);
knownViews.add(desc);
}
/**
* Creates and return a new <merge> descriptor.
* @param knownLayouts A list of all known layout view descriptors, used to find the
* FrameLayout descriptor and extract its layout attributes.
*/
private ViewElementDescriptor createMerge(List<ViewElementDescriptor> knownLayouts) {
String xml_name = VIEW_MERGE;
// Find View and inherit all its layout attributes
AttributeDescriptor[] viewLayoutAttribs = findViewLayoutAttributes(
SdkConstants.CLASS_FRAMELAYOUT, knownLayouts);
// Create the include descriptor
ViewElementDescriptor desc = new ViewElementDescriptor(xml_name, // xml_name
xml_name, // ui_name
VIEW_MERGE, // "class name"; the GLE only treats this as an element tag
"A root tag useful for XML layouts inflated using a ViewStub.", // tooltip
null, // sdk_url
null, // attributes
viewLayoutAttribs, // layout attributes
null, // children
false /* mandatory */);
return desc;
}
/**
* Finds the descriptor and retrieves all its layout attributes.
*/
private AttributeDescriptor[] findViewLayoutAttributes(
String viewFqcn,
List<ViewElementDescriptor> knownViews) {
for (ViewElementDescriptor viewDesc : knownViews) {
if (viewFqcn.equals(viewDesc.getFullClassName())) {
return viewDesc.getLayoutAttributes();
}
}
return null;
}
/**
* Set the super-class of each {@link ViewElementDescriptor} by using the super-class
* information available in the {@link ViewClassInfo}.
*/
private void fixSuperClasses(Map<ViewClassInfo, ViewElementDescriptor> infoDescMap) {
for (Entry<ViewClassInfo, ViewElementDescriptor> entry : infoDescMap.entrySet()) {
ViewClassInfo info = entry.getKey();
ViewElementDescriptor desc = entry.getValue();
ViewClassInfo sup = info.getSuperClass();
if (sup != null) {
ViewElementDescriptor supDesc = infoDescMap.get(sup);
while (supDesc == null && sup != null) {
// We don't have a descriptor for the super-class. That means the class is
// probably abstract, so we just need to walk up the super-class chain till
// we find one we have. All views derive from android.view.View so we should
// surely find that eventually.
sup = sup.getSuperClass();
if (sup != null) {
supDesc = infoDescMap.get(sup);
}
}
if (supDesc != null) {
desc.setSuperClass(supDesc);
}
}
}
}
}