1 /* 2 * Copyright (C) 2007 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.descriptors; 18 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 21 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; 22 import com.android.sdklib.SdkConstants; 23 24 import org.eclipse.jface.resource.ImageDescriptor; 25 import org.eclipse.swt.graphics.Image; 26 27 import java.util.Collection; 28 import java.util.HashSet; 29 import java.util.Set; 30 31 /** 32 * {@link ElementDescriptor} describes the properties expected for a given XML element node. 33 * 34 * {@link ElementDescriptor} have an XML name, UI name, a tooltip, an SDK url, 35 * an attributes list and a children list. 36 * 37 * An UI node can be "mandatory", meaning the UI node is never deleted and it may lack 38 * an actual XML node attached. A non-mandatory UI node MUST have an XML node attached 39 * and it will cease to exist when the XML node ceases to exist. 40 */ 41 public class ElementDescriptor { 42 /** The XML element node name. Case sensitive. */ 43 private String mXmlName; 44 /** The XML element name for the user interface, typically capitalized. */ 45 private String mUiName; 46 /** The list of allowed attributes. */ 47 private AttributeDescriptor[] mAttributes; 48 /** The list of allowed children */ 49 private ElementDescriptor[] mChildren; 50 /* An optional tooltip. Can be empty. */ 51 private String mTooltip; 52 /** An optional SKD URL. Can be empty. */ 53 private String mSdkUrl; 54 /** Whether this UI node must always exist (even for empty models). */ 55 private boolean mMandatory; 56 57 /** 58 * Constructs a new {@link ElementDescriptor} based on its XML name, UI name, 59 * tooltip, SDK url, attributes list, children list and mandatory. 60 * 61 * @param xml_name The XML element node name. Case sensitive. 62 * @param ui_name The XML element name for the user interface, typically capitalized. 63 * @param tooltip An optional tooltip. Can be null or empty. 64 * @param sdk_url An optional SKD URL. Can be null or empty. 65 * @param attributes The list of allowed attributes. Can be null or empty. 66 * @param children The list of allowed children. Can be null or empty. 67 * @param mandatory Whether this node must always exist (even for empty models). A mandatory 68 * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory 69 * UI node MUST have an XML node attached and it will cease to exist when the XML node 70 * ceases to exist. 71 */ ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, AttributeDescriptor[] attributes, ElementDescriptor[] children, boolean mandatory)72 public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, 73 AttributeDescriptor[] attributes, 74 ElementDescriptor[] children, 75 boolean mandatory) { 76 mMandatory = mandatory; 77 mXmlName = xml_name; 78 mUiName = ui_name; 79 mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null; 80 mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null; 81 setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{}); 82 mChildren = children != null ? children : new ElementDescriptor[]{}; 83 } 84 85 /** 86 * Constructs a new {@link ElementDescriptor} based on its XML name and children list. 87 * The UI name is build by capitalizing the XML name. 88 * The UI nodes will be non-mandatory. 89 * 90 * @param xml_name The XML element node name. Case sensitive. 91 * @param children The list of allowed children. Can be null or empty. 92 * @param mandatory Whether this node must always exist (even for empty models). A mandatory 93 * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory 94 * UI node MUST have an XML node attached and it will cease to exist when the XML node 95 * ceases to exist. 96 */ ElementDescriptor(String xml_name, ElementDescriptor[] children, boolean mandatory)97 public ElementDescriptor(String xml_name, ElementDescriptor[] children, boolean mandatory) { 98 this(xml_name, prettyName(xml_name), null, null, null, children, mandatory); 99 } 100 101 /** 102 * Constructs a new {@link ElementDescriptor} based on its XML name and children list. 103 * The UI name is build by capitalizing the XML name. 104 * The UI nodes will be non-mandatory. 105 * 106 * @param xml_name The XML element node name. Case sensitive. 107 * @param children The list of allowed children. Can be null or empty. 108 */ ElementDescriptor(String xml_name, ElementDescriptor[] children)109 public ElementDescriptor(String xml_name, ElementDescriptor[] children) { 110 this(xml_name, prettyName(xml_name), null, null, null, children, false); 111 } 112 113 /** 114 * Constructs a new {@link ElementDescriptor} based on its XML name. 115 * The UI name is build by capitalizing the XML name. 116 * The UI nodes will be non-mandatory. 117 * 118 * @param xml_name The XML element node name. Case sensitive. 119 */ ElementDescriptor(String xml_name)120 public ElementDescriptor(String xml_name) { 121 this(xml_name, prettyName(xml_name), null, null, null, null, false); 122 } 123 124 /** Returns whether this node must always exist (even for empty models) */ isMandatory()125 public boolean isMandatory() { 126 return mMandatory; 127 } 128 129 /** 130 * Returns the XML element node local name (case sensitive) 131 */ getXmlLocalName()132 public final String getXmlLocalName() { 133 int pos = mXmlName.indexOf(':'); 134 if (pos != -1) { 135 return mXmlName.substring(pos+1); 136 } 137 return mXmlName; 138 } 139 140 /** 141 * Returns the XML element node name, including the prefix. 142 * Case sensitive. 143 * <p/> 144 * In Android resources, the element node name for Android resources typically does not 145 * have a prefix and is typically the simple Java class name (e.g. "View"), whereas for 146 * custom views it is generally the fully qualified class name of the view (e.g. 147 * "com.mycompany.myapp.MyView"). 148 * <p/> 149 * Most of the time you'll probably want to use {@link #getXmlLocalName()} to get a local 150 * name guaranteed without a prefix. 151 * <p/> 152 * Note that the prefix that <em>may</em> be available in this descriptor has nothing to 153 * do with the actual prefix the node might have (or needs to have) in the actual XML file 154 * since descriptors are fixed and do not depend on any current namespace defined in the 155 * target XML. 156 */ getXmlName()157 public String getXmlName() { 158 return mXmlName; 159 } 160 161 /** 162 * Returns the namespace of the attribute. 163 */ getNamespace()164 public final String getNamespace() { 165 // For now we hard-code the prefix as being "android" 166 if (mXmlName.startsWith("android:")) { //$NON-NLs-1$ 167 return SdkConstants.NS_RESOURCES; 168 } 169 170 return ""; //$NON-NLs-1$ 171 } 172 173 174 /** Returns the XML element name for the user interface, typically capitalized. */ getUiName()175 public String getUiName() { 176 return mUiName; 177 } 178 179 /** 180 * Returns an optional icon for the element. 181 * <p/> 182 * By default this tries to return an icon based on the XML name of the element. 183 * If this fails, it tries to return the default Android logo as defined in the 184 * plugin. If all fails, it returns null. 185 * 186 * @return An icon for this element or null. 187 */ getIcon()188 public Image getIcon() { 189 IconFactory factory = IconFactory.getInstance(); 190 int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN; 191 int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE; 192 Image icon = factory.getIcon(mXmlName, color, shape); 193 return icon != null ? icon : AdtPlugin.getAndroidLogo(); 194 } 195 196 /** 197 * Returns an optional ImageDescriptor for the element. 198 * <p/> 199 * By default this tries to return an image based on the XML name of the element. 200 * If this fails, it tries to return the default Android logo as defined in the 201 * plugin. If all fails, it returns null. 202 * 203 * @return An ImageDescriptor for this element or null. 204 */ getImageDescriptor()205 public ImageDescriptor getImageDescriptor() { 206 IconFactory factory = IconFactory.getInstance(); 207 int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN; 208 int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE; 209 ImageDescriptor id = factory.getImageDescriptor(mXmlName, color, shape); 210 return id != null ? id : AdtPlugin.getAndroidLogoDesc(); 211 } 212 213 /* Returns the list of allowed attributes. */ getAttributes()214 public AttributeDescriptor[] getAttributes() { 215 return mAttributes; 216 } 217 218 /* Sets the list of allowed attributes. */ setAttributes(AttributeDescriptor[] attributes)219 public void setAttributes(AttributeDescriptor[] attributes) { 220 mAttributes = attributes; 221 for (AttributeDescriptor attribute : attributes) { 222 attribute.setParent(this); 223 } 224 } 225 226 /** Returns the list of allowed children */ getChildren()227 public ElementDescriptor[] getChildren() { 228 return mChildren; 229 } 230 231 /** @return True if this descriptor has children available */ hasChildren()232 public boolean hasChildren() { 233 return mChildren.length > 0; 234 } 235 236 /** Sets the list of allowed children. */ setChildren(ElementDescriptor[] newChildren)237 public void setChildren(ElementDescriptor[] newChildren) { 238 mChildren = newChildren; 239 } 240 241 /** 242 * Sets the list of allowed children. 243 * <p/> 244 * This is just a convenience method that converts a Collection into an array and 245 * calls {@link #setChildren(ElementDescriptor[])}. 246 * <p/> 247 * This means a <em>copy</em> of the collection is made. The collection is not 248 * stored by the recipient and can thus be altered by the caller. 249 */ setChildren(Collection<ElementDescriptor> newChildren)250 public void setChildren(Collection<ElementDescriptor> newChildren) { 251 setChildren(newChildren.toArray(new ElementDescriptor[newChildren.size()])); 252 } 253 254 /** 255 * Returns an optional tooltip. Will be null if not present. 256 * <p/> 257 * The tooltip is based on the Javadoc of the element and already processed via 258 * {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as 259 * a UI tooltip. 260 */ getTooltip()261 public String getTooltip() { 262 return mTooltip; 263 } 264 265 /** Returns an optional SKD URL. Will be null if not present. */ getSdkUrl()266 public String getSdkUrl() { 267 return mSdkUrl; 268 } 269 270 /** Sets the optional tooltip. Can be null or empty. */ setTooltip(String tooltip)271 public void setTooltip(String tooltip) { 272 mTooltip = tooltip; 273 } 274 275 /** Sets the optional SDK URL. Can be null or empty. */ setSdkUrl(String sdkUrl)276 public void setSdkUrl(String sdkUrl) { 277 mSdkUrl = sdkUrl; 278 } 279 280 /** 281 * @return A new {@link UiElementNode} linked to this descriptor. 282 */ createUiNode()283 public UiElementNode createUiNode() { 284 return new UiElementNode(this); 285 } 286 287 /** 288 * Returns the first children of this descriptor that describes the given XML element name. 289 * <p/> 290 * In recursive mode, searches the direct children first before descending in the hierarchy. 291 * 292 * @return The ElementDescriptor matching the requested XML node element name or null. 293 */ findChildrenDescriptor(String element_name, boolean recursive)294 public ElementDescriptor findChildrenDescriptor(String element_name, boolean recursive) { 295 return findChildrenDescriptorInternal(element_name, recursive, null); 296 } 297 findChildrenDescriptorInternal(String element_name, boolean recursive, Set<ElementDescriptor> visited)298 private ElementDescriptor findChildrenDescriptorInternal(String element_name, 299 boolean recursive, 300 Set<ElementDescriptor> visited) { 301 if (recursive && visited == null) { 302 visited = new HashSet<ElementDescriptor>(); 303 } 304 305 for (ElementDescriptor e : getChildren()) { 306 if (e.getXmlName().equals(element_name)) { 307 return e; 308 } 309 } 310 311 if (visited != null) { 312 visited.add(this); 313 } 314 315 if (recursive) { 316 for (ElementDescriptor e : getChildren()) { 317 if (visited != null) { 318 if (!visited.add(e)) { // Set.add() returns false if element is already present 319 continue; 320 } 321 } 322 ElementDescriptor f = e.findChildrenDescriptorInternal(element_name, 323 recursive, visited); 324 if (f != null) { 325 return f; 326 } 327 } 328 } 329 330 return null; 331 } 332 333 /** 334 * Utility helper than pretty-formats an XML Name for the UI. 335 * This is used by the simplified constructor that takes only an XML element name. 336 * 337 * @param xml_name The XML name to convert. 338 * @return The XML name with dashes replaced by spaces and capitalized. 339 */ prettyName(String xml_name)340 private static String prettyName(String xml_name) { 341 char c[] = xml_name.toCharArray(); 342 if (c.length > 0) { 343 c[0] = Character.toUpperCase(c[0]); 344 } 345 return new String(c).replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$ 346 } 347 348 } 349