Home | History | Annotate | Download | only in gre
      1 /*
      2  * Copyright (C) 2011 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.gre;
     18 
     19 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
     20 import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
     21 import static com.android.ide.common.layout.LayoutConstants.FQCN_BUTTON;
     22 import static com.android.ide.common.layout.LayoutConstants.FQCN_SPINNER;
     23 import static com.android.ide.common.layout.LayoutConstants.FQCN_TOGGLE_BUTTON;
     24 import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX;
     25 import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
     26 
     27 import com.android.annotations.VisibleForTesting;
     28 import com.android.ide.common.api.IViewMetadata.FillPreference;
     29 import com.android.ide.common.api.Margins;
     30 import com.android.ide.common.api.ResizePolicy;
     31 import com.android.ide.eclipse.adt.AdtPlugin;
     32 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     33 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
     34 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
     35 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     36 import com.android.resources.Density;
     37 import com.android.util.Pair;
     38 
     39 import org.w3c.dom.Document;
     40 import org.w3c.dom.Element;
     41 import org.w3c.dom.Node;
     42 import org.w3c.dom.NodeList;
     43 import org.xml.sax.InputSource;
     44 
     45 import java.io.BufferedInputStream;
     46 import java.io.InputStream;
     47 import java.util.ArrayList;
     48 import java.util.Collection;
     49 import java.util.Collections;
     50 import java.util.HashMap;
     51 import java.util.HashSet;
     52 import java.util.Iterator;
     53 import java.util.List;
     54 import java.util.Map;
     55 import java.util.Set;
     56 
     57 import javax.xml.parsers.DocumentBuilder;
     58 import javax.xml.parsers.DocumentBuilderFactory;
     59 
     60 /**
     61  * The {@link ViewMetadataRepository} contains additional metadata for Android view
     62  * classes
     63  */
     64 public class ViewMetadataRepository {
     65     private static final String PREVIEW_CONFIG_FILENAME = "rendering-configs.xml";  //$NON-NLS-1$
     66     private static final String METADATA_FILENAME = "extra-view-metadata.xml";  //$NON-NLS-1$
     67 
     68     /** Singleton instance */
     69     private static ViewMetadataRepository sInstance = new ViewMetadataRepository();
     70 
     71     /**
     72      * Returns the singleton instance
     73      *
     74      * @return the {@link ViewMetadataRepository}
     75      */
     76     public static ViewMetadataRepository get() {
     77         return sInstance;
     78     }
     79 
     80     /**
     81      * Ever increasing counter used to assign natural ordering numbers to views and
     82      * categories
     83      */
     84     private static int sNextOrdinal = 0;
     85 
     86     /**
     87      * List of categories (which contain views); constructed lazily so use
     88      * {@link #getCategories()}
     89      */
     90     private List<CategoryData> mCategories;
     91 
     92     /**
     93      * Map from class names to view data objects; constructed lazily so use
     94      * {@link #getClassToView}
     95      */
     96     private Map<String, ViewData> mClassToView;
     97 
     98     /** Hidden constructor: Create via factory {@link #get()} instead */
     99     private ViewMetadataRepository() {
    100     }
    101 
    102     /** Returns a map from class fully qualified names to {@link ViewData} objects */
    103     private Map<String, ViewData> getClassToView() {
    104         if (mClassToView == null) {
    105             int initialSize = 75;
    106             mClassToView = new HashMap<String, ViewData>(initialSize);
    107             List<CategoryData> categories = getCategories();
    108             for (CategoryData category : categories) {
    109                 for (ViewData view : category) {
    110                     mClassToView.put(view.getFcqn(), view);
    111                 }
    112             }
    113             assert mClassToView.size() <= initialSize;
    114         }
    115 
    116         return mClassToView;
    117     }
    118 
    119     /**
    120      * Returns an XML document containing rendering configurations for the various Android
    121      * views. The FQN of each view can be obtained via the
    122      * {@link #getFullClassName(Element)} method
    123      *
    124      * @return an XML document containing rendering elements
    125      */
    126     public Document getRenderingConfigDoc() {
    127         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    128         Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
    129         InputStream paletteStream = clz.getResourceAsStream(PREVIEW_CONFIG_FILENAME);
    130         InputSource is = new InputSource(paletteStream);
    131         try {
    132             factory.setNamespaceAware(true);
    133             factory.setValidating(false);
    134             factory.setIgnoringComments(true);
    135             DocumentBuilder builder = factory.newDocumentBuilder();
    136             return builder.parse(is);
    137         } catch (Exception e) {
    138             AdtPlugin.log(e, "Parsing palette file failed");
    139             return null;
    140         }
    141     }
    142 
    143     /**
    144      * Returns a fully qualified class name for an element in the rendering document
    145      * returned by {@link #getRenderingConfigDoc()}
    146      *
    147      * @param element the element to look up the fqcn for
    148      * @return the fqcn of the view the element represents a preview for
    149      */
    150     public String getFullClassName(Element element) {
    151         // We don't use the element tag name, because in some cases we have
    152         // an outer element to render some interesting inner element, such as a tab widget
    153         // (which must be rendered inside a tab host).
    154         //
    155         // Therefore, we instead use the convention that the id is the fully qualified
    156         // class name, with .'s replaced with _'s.
    157 
    158         // Special case: for tab host we aren't allowed to mess with the id
    159         String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
    160 
    161         if ("@android:id/tabhost".equals(id)) {
    162             // Special case to distinguish TabHost and TabWidget
    163             NodeList children = element.getChildNodes();
    164             if (children.getLength() > 1 && (children.item(1) instanceof Element)) {
    165                 Element child = (Element) children.item(1);
    166                 String childId = child.getAttributeNS(ANDROID_URI, ATTR_ID);
    167                 if ("@+id/android_widget_TabWidget".equals(childId)) {
    168                     return "android.widget.TabWidget"; // TODO: Tab widget!
    169                 }
    170             }
    171             return "android.widget.TabHost"; // TODO: Tab widget!
    172         }
    173 
    174         StringBuilder sb = new StringBuilder();
    175         int i = 0;
    176         if (id.startsWith(NEW_ID_PREFIX)) {
    177             i = NEW_ID_PREFIX.length();
    178         } else if (id.startsWith(ID_PREFIX)) {
    179             i = ID_PREFIX.length();
    180         }
    181 
    182         for (; i < id.length(); i++) {
    183             char c = id.charAt(i);
    184             if (c == '_') {
    185                 sb.append('.');
    186             } else {
    187                 sb.append(c);
    188             }
    189         }
    190 
    191         return sb.toString();
    192     }
    193 
    194     /** Returns an ordered list of categories and views, parsed from a metadata file */
    195     private List<CategoryData> getCategories() {
    196         if (mCategories == null) {
    197             mCategories = new ArrayList<CategoryData>();
    198 
    199             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    200             Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
    201             InputStream inputStream = clz.getResourceAsStream(METADATA_FILENAME);
    202             InputSource is = new InputSource(new BufferedInputStream(inputStream));
    203             try {
    204                 factory.setNamespaceAware(true);
    205                 factory.setValidating(false);
    206                 factory.setIgnoringComments(true);
    207                 DocumentBuilder builder = factory.newDocumentBuilder();
    208                 Document document = builder.parse(is);
    209                 Map<String, FillPreference> fillTypes = new HashMap<String, FillPreference>();
    210                 for (FillPreference pref : FillPreference.values()) {
    211                     fillTypes.put(pref.toString().toLowerCase(), pref);
    212                 }
    213 
    214                 NodeList categoryNodes = document.getDocumentElement().getChildNodes();
    215                 for (int i = 0, n = categoryNodes.getLength(); i < n; i++) {
    216                     Node node = categoryNodes.item(i);
    217                     if (node.getNodeType() == Node.ELEMENT_NODE) {
    218                         Element element = (Element) node;
    219                         if (element.getNodeName().equals("category")) { //$NON-NLS-1$
    220                             String name = element.getAttribute("name"); //$NON-NLS-1$
    221                             CategoryData category = new CategoryData(name);
    222                             NodeList children = element.getChildNodes();
    223                             for (int j = 0, m = children.getLength(); j < m; j++) {
    224                                 Node childNode = children.item(j);
    225                                 if (childNode.getNodeType() == Node.ELEMENT_NODE) {
    226                                     Element child = (Element) childNode;
    227                                     ViewData view = createViewData(fillTypes, child,
    228                                             null, FillPreference.NONE, RenderMode.NORMAL, null);
    229                                     category.addView(view);
    230                                 }
    231                             }
    232                             mCategories.add(category);
    233                         }
    234                     }
    235                 }
    236             } catch (Exception e) {
    237                 AdtPlugin.log(e, "Invalid palette metadata"); //$NON-NLS-1$
    238             }
    239         }
    240 
    241         return mCategories;
    242     }
    243 
    244     private ViewData createViewData(Map<String, FillPreference> fillTypes,
    245             Element child, String defaultFqcn, FillPreference defaultFill,
    246             RenderMode defaultRender, String defaultSize) {
    247         String fqcn = child.getAttribute("class"); //$NON-NLS-1$
    248         if (fqcn.length() == 0) {
    249             fqcn = defaultFqcn;
    250         }
    251         String fill = child.getAttribute("fill"); //$NON-NLS-1$
    252         FillPreference fillPreference = null;
    253         if (fill.length() > 0) {
    254             fillPreference = fillTypes.get(fill);
    255         }
    256         if (fillPreference == null) {
    257             fillPreference = defaultFill;
    258         }
    259         String skip = child.getAttribute("skip"); //$NON-NLS-1$
    260         RenderMode renderMode = defaultRender;
    261         String render = child.getAttribute("render"); //$NON-NLS-1$
    262         if (render.length() > 0) {
    263             renderMode = RenderMode.get(render);
    264         }
    265         String displayName = child.getAttribute("name"); //$NON-NLS-1$
    266         if (displayName.length() == 0) {
    267             displayName = null;
    268         }
    269 
    270         String relatedTo = child.getAttribute("relatedTo"); //$NON-NLS-1$
    271         String topAttrs = child.getAttribute("topAttrs"); //$NON-NLS-1$
    272         String resize = child.getAttribute("resize"); //$NON-NLS-1$
    273         ViewData view = new ViewData(fqcn, displayName, fillPreference,
    274                 skip.length() == 0 ? false : Boolean.valueOf(skip),
    275                 renderMode, relatedTo, resize, topAttrs);
    276 
    277         String init = child.getAttribute("init"); //$NON-NLS-1$
    278         String icon = child.getAttribute("icon"); //$NON-NLS-1$
    279 
    280         view.setInitString(init);
    281         if (icon.length() > 0) {
    282             view.setIconName(icon);
    283         }
    284 
    285         // Nested variations?
    286         if (child.hasChildNodes()) {
    287             // Palette variations
    288             NodeList childNodes = child.getChildNodes();
    289             for (int k = 0, kl = childNodes.getLength(); k < kl; k++) {
    290                 Node variationNode = childNodes.item(k);
    291                 if (variationNode.getNodeType() == Node.ELEMENT_NODE) {
    292                     Element variation = (Element) variationNode;
    293                     ViewData variationView = createViewData(fillTypes, variation,
    294                             fqcn, fillPreference, renderMode, resize);
    295                     view.addVariation(variationView);
    296                 }
    297             }
    298         }
    299 
    300         return view;
    301     }
    302 
    303     /**
    304      * Computes the palette entries for the given {@link AndroidTargetData}, looking up the
    305      * available node descriptors, categorizing and sorting them.
    306      *
    307      * @param targetData the target data for which to compute palette entries
    308      * @param alphabetical if true, sort all items in alphabetical order
    309      * @param createCategories if true, organize the items into categories
    310      * @return a list of pairs where each pair contains of the category label and an
    311      *         ordered list of elements to be included in that category
    312      */
    313     public List<Pair<String, List<ViewElementDescriptor>>> getPaletteEntries(
    314             AndroidTargetData targetData, boolean alphabetical, boolean createCategories) {
    315         List<Pair<String, List<ViewElementDescriptor>>> result =
    316             new ArrayList<Pair<String, List<ViewElementDescriptor>>>();
    317 
    318         List<List<ViewElementDescriptor>> lists = new ArrayList<List<ViewElementDescriptor>>(2);
    319         LayoutDescriptors layoutDescriptors = targetData.getLayoutDescriptors();
    320         lists.add(layoutDescriptors.getViewDescriptors());
    321         lists.add(layoutDescriptors.getLayoutDescriptors());
    322 
    323         // First record map of FQCN to ViewElementDescriptor such that we can quickly
    324         // determine if a particular palette entry is available
    325         Map<String, ViewElementDescriptor> fqcnToDescriptor =
    326             new HashMap<String, ViewElementDescriptor>();
    327         for (List<ViewElementDescriptor> list : lists) {
    328             for (ViewElementDescriptor view : list) {
    329                 String fqcn = view.getFullClassName();
    330                 if (fqcn == null) {
    331                     // <view> and <merge> tags etc
    332                     fqcn = view.getUiName();
    333                 }
    334                 fqcnToDescriptor.put(fqcn, view);
    335             }
    336         }
    337 
    338         Set<ViewElementDescriptor> remaining = new HashSet<ViewElementDescriptor>(
    339                 layoutDescriptors.getViewDescriptors().size()
    340                 + layoutDescriptors.getLayoutDescriptors().size());
    341         remaining.addAll(layoutDescriptors.getViewDescriptors());
    342         remaining.addAll(layoutDescriptors.getLayoutDescriptors());
    343 
    344         // Now iterate in palette metadata order over the items in the palette and include
    345         // any that also appear as a descriptor
    346         List<ViewElementDescriptor> categoryItems = new ArrayList<ViewElementDescriptor>();
    347         for (CategoryData category : getCategories()) {
    348             if (createCategories) {
    349                 categoryItems = new ArrayList<ViewElementDescriptor>();
    350             }
    351             for (ViewData view : category) {
    352                 String fqcn = view.getFcqn();
    353                 ViewElementDescriptor descriptor = fqcnToDescriptor.get(fqcn);
    354                 if (descriptor != null) {
    355                     remaining.remove(descriptor);
    356                     if (view.getSkip()) {
    357                         continue;
    358                     }
    359 
    360                     if (view.getDisplayName() != null || view.getInitString().length() > 0) {
    361                         categoryItems.add(new PaletteMetadataDescriptor(descriptor,
    362                                 view.getDisplayName(), view.getInitString(), view.getIconName()));
    363                     } else {
    364                         categoryItems.add(descriptor);
    365                     }
    366 
    367                     if (view.hasVariations()) {
    368                         for (ViewData variation : view.getVariations()) {
    369                             String init = variation.getInitString();
    370                             String icon = variation.getIconName();
    371                             ViewElementDescriptor desc = new PaletteMetadataDescriptor(descriptor,
    372                                     variation.getDisplayName(), init, icon);
    373                             categoryItems.add(desc);
    374                         }
    375                     }
    376                 }
    377             }
    378 
    379             if (createCategories && categoryItems.size() > 0) {
    380                 if (alphabetical) {
    381                     Collections.sort(categoryItems);
    382                 }
    383                 result.add(Pair.of(category.getName(), categoryItems));
    384             }
    385         }
    386 
    387         if (remaining.size() > 0) {
    388             List<ViewElementDescriptor> otherItems =
    389                     new ArrayList<ViewElementDescriptor>(remaining);
    390             // Always sorted, we don't have a natural order for these unknowns
    391             Collections.sort(otherItems);
    392             if (createCategories) {
    393                 result.add(Pair.of("Other", otherItems));
    394             } else {
    395                 categoryItems.addAll(otherItems);
    396             }
    397         }
    398 
    399         if (!createCategories) {
    400             if (alphabetical) {
    401                 Collections.sort(categoryItems);
    402             }
    403             result.add(Pair.of("Views", categoryItems));
    404         }
    405 
    406         return result;
    407     }
    408 
    409     @VisibleForTesting
    410     Collection<String> getAllFqcns() {
    411         return getClassToView().keySet();
    412     }
    413 
    414     /**
    415      * Metadata holder for a particular category - contains the name of the category, its
    416      * ordinal (for natural/logical sorting order) and views contained in the category
    417      */
    418     private static class CategoryData implements Iterable<ViewData>, Comparable<CategoryData> {
    419         /** Category name */
    420         private final String mName;
    421         /** Views included in this category */
    422         private final List<ViewData> mViews = new ArrayList<ViewData>();
    423         /** Natural ordering rank */
    424         private final int mOrdinal = sNextOrdinal++;
    425 
    426         /** Constructs a new category with the given name */
    427         private CategoryData(String name) {
    428             super();
    429             mName = name;
    430         }
    431 
    432         /** Adds a new view into this category */
    433         private void addView(ViewData view) {
    434             mViews.add(view);
    435         }
    436 
    437         private String getName() {
    438             return mName;
    439         }
    440 
    441         // Implements Iterable<ViewData> such that we can use for-each on the category to
    442         // enumerate its views
    443         public Iterator<ViewData> iterator() {
    444             return mViews.iterator();
    445         }
    446 
    447         // Implements Comparable<CategoryData> such that categories can be naturally sorted
    448         public int compareTo(CategoryData other) {
    449             return mOrdinal - other.mOrdinal;
    450         }
    451     }
    452 
    453     /** Metadata holder for a view of a given fully qualified class name */
    454     private static class ViewData implements Comparable<ViewData> {
    455         /** The fully qualified class name of the view */
    456         private final String mFqcn;
    457         /** Fill preference of the view */
    458         private final FillPreference mFillPreference;
    459         /** Skip this item in the palette? */
    460         private final boolean mSkip;
    461         /** Must this item be rendered alone? skipped? etc */
    462         private final RenderMode mRenderMode;
    463         /** Related views */
    464         private final String mRelatedTo;
    465         /** The relative rank of the view for natural ordering */
    466         private final int mOrdinal = sNextOrdinal++;
    467         /** List of optional variations */
    468         private List<ViewData> mVariations;
    469         /** Display name. Can be null. */
    470         private String mDisplayName;
    471         /**
    472          * Optional initialization string - a comma separate set of name/value pairs to
    473          * initialize the element with
    474          */
    475         private String mInitString;
    476         /** The name of an icon (known to the {@link IconFactory} to show for this view */
    477         private String mIconName;
    478         /** The resize preference of this view */
    479         private String mResize;
    480         /** The most commonly set attributes of this view */
    481         private String mTopAttrs;
    482 
    483         /** Constructs a new view data for the given class */
    484         private ViewData(String fqcn, String displayName,
    485                 FillPreference fillPreference, boolean skip, RenderMode renderMode,
    486                 String relatedTo, String resize, String topAttrs) {
    487             super();
    488             mFqcn = fqcn;
    489             mDisplayName = displayName;
    490             mFillPreference = fillPreference;
    491             mSkip = skip;
    492             mRenderMode = renderMode;
    493             mRelatedTo = relatedTo;
    494             mResize = resize;
    495             mTopAttrs = topAttrs;
    496         }
    497 
    498         /** Returns the {@link FillPreference} for views of this type */
    499         private FillPreference getFillPreference() {
    500             return mFillPreference;
    501         }
    502 
    503         /** Fully qualified class name of views of this type */
    504         private String getFcqn() {
    505             return mFqcn;
    506         }
    507 
    508         private String getDisplayName() {
    509             return mDisplayName;
    510         }
    511 
    512         private String getResize() {
    513             return mResize;
    514         }
    515 
    516         // Implements Comparable<ViewData> such that views can be sorted naturally
    517         public int compareTo(ViewData other) {
    518             return mOrdinal - other.mOrdinal;
    519         }
    520 
    521         public RenderMode getRenderMode() {
    522             return mRenderMode;
    523         }
    524 
    525         public boolean getSkip() {
    526             return mSkip;
    527         }
    528 
    529         public List<String> getRelatedTo() {
    530             if (mRelatedTo == null || mRelatedTo.length() == 0) {
    531                 return Collections.emptyList();
    532             } else {
    533                 String[] basenames = mRelatedTo.split(","); //$NON-NLS-1$
    534                 List<String> result = new ArrayList<String>();
    535                 ViewMetadataRepository repository = ViewMetadataRepository.get();
    536                 Map<String, ViewData> classToView = repository.getClassToView();
    537 
    538                 List<String> fqns = new ArrayList<String>(classToView.keySet());
    539                 for (String basename : basenames) {
    540                     boolean found = false;
    541                     for (String fqcn : fqns) {
    542                         String suffix = '.' + basename;
    543                         if (fqcn.endsWith(suffix)) {
    544                             result.add(fqcn);
    545                             found = true;
    546                             break;
    547                         }
    548                     }
    549                     assert found : basename;
    550                 }
    551 
    552                 return result;
    553             }
    554         }
    555 
    556         public List<String> getTopAttributes() {
    557             // "id" is a top attribute for all views, so it is not included in the XML, we just
    558             // add it in dynamically here
    559             if (mTopAttrs == null || mTopAttrs.length() == 0) {
    560                 return Collections.singletonList(ATTR_ID);
    561             } else {
    562                 String[] split = mTopAttrs.split(","); //$NON-NLS-1$
    563                 List<String> topAttributes = new ArrayList<String>(split.length + 1);
    564                 topAttributes.add(ATTR_ID);
    565                 for (int i = 0, n = split.length; i < n; i++) {
    566                     topAttributes.add(split[i]);
    567                 }
    568                 return Collections.<String>unmodifiableList(topAttributes);
    569             }
    570         }
    571 
    572         void addVariation(ViewData variation) {
    573             if (mVariations == null) {
    574                 mVariations = new ArrayList<ViewData>(4);
    575             }
    576             mVariations.add(variation);
    577         }
    578 
    579         List<ViewData> getVariations() {
    580             return mVariations;
    581         }
    582 
    583         boolean hasVariations() {
    584             return mVariations != null && mVariations.size() > 0;
    585         }
    586 
    587         private void setInitString(String initString) {
    588             this.mInitString = initString;
    589         }
    590 
    591         private String getInitString() {
    592             return mInitString;
    593         }
    594 
    595         private void setIconName(String iconName) {
    596             this.mIconName = iconName;
    597         }
    598 
    599         private String getIconName() {
    600             return mIconName;
    601         }
    602     }
    603 
    604     /**
    605      * Returns the {@link FillPreference} for classes with the given fully qualified class
    606      * name
    607      *
    608      * @param fqcn the fully qualified class name of the view
    609      * @return a suitable {@link FillPreference} for the given view type
    610      */
    611     public FillPreference getFillPreference(String fqcn) {
    612         ViewData view = getClassToView().get(fqcn);
    613         if (view != null) {
    614             return view.getFillPreference();
    615         }
    616 
    617         return FillPreference.NONE;
    618     }
    619 
    620     /**
    621      * Returns the {@link RenderMode} for classes with the given fully qualified class
    622      * name
    623      *
    624      * @param fqcn the fully qualified class name
    625      * @return the {@link RenderMode} to use for previews of the given view type
    626      */
    627     public RenderMode getRenderMode(String fqcn) {
    628         ViewData view = getClassToView().get(fqcn);
    629         if (view != null) {
    630             return view.getRenderMode();
    631         }
    632 
    633         return RenderMode.NORMAL;
    634     }
    635 
    636     /**
    637      * Returns the {@link ResizePolicy} for the given class.
    638      *
    639      * @param fqcn the fully qualified class name of the target widget
    640      * @return the {@link ResizePolicy} for the widget, which will never be null (but may
    641      *         be the default of {@link ResizePolicy#full()} if no metadata is found for
    642      *         the given widget)
    643      */
    644     public ResizePolicy getResizePolicy(String fqcn) {
    645         ViewData view = getClassToView().get(fqcn);
    646         if (view != null) {
    647             String resize = view.getResize();
    648             if (resize != null && resize.length() > 0) {
    649                 if ("full".equals(resize)) { //$NON-NLS-1$
    650                     return ResizePolicy.full();
    651                 } else if ("none".equals(resize)) { //$NON-NLS-1$
    652                     return ResizePolicy.none();
    653                 } else if ("horizontal".equals(resize)) { //$NON-NLS-1$
    654                     return ResizePolicy.horizontal();
    655                 } else if ("vertical".equals(resize)) { //$NON-NLS-1$
    656                     return ResizePolicy.vertical();
    657                 } else if ("scaled".equals(resize)) { //$NON-NLS-1$
    658                     return ResizePolicy.scaled();
    659                 } else {
    660                     assert false : resize;
    661                 }
    662             }
    663         }
    664 
    665         return ResizePolicy.full();
    666     }
    667 
    668     /**
    669      * Returns true if classes with the given fully qualified class name should be hidden
    670      * or skipped from the palette
    671      *
    672      * @param fqcn the fully qualified class name
    673      * @return true if views of the given type should be hidden from the palette
    674      */
    675     public boolean getSkip(String fqcn) {
    676         ViewData view = getClassToView().get(fqcn);
    677         if (view != null) {
    678             return view.getSkip();
    679         }
    680 
    681         return false;
    682     }
    683 
    684     /**
    685      * Returns a list of the top (most commonly set) attributes of the given
    686      * view.
    687      *
    688      * @param fqcn the fully qualified class name
    689      * @return a list, never null but possibly empty, of popular attribute names
    690      *         (not including a namespace prefix)
    691      */
    692     public List<String> getTopAttributes(String fqcn) {
    693         ViewData view = getClassToView().get(fqcn);
    694         if (view != null) {
    695             return view.getTopAttributes();
    696         }
    697 
    698         return Collections.singletonList(ATTR_ID);
    699     }
    700 
    701     /**
    702      * Returns a set of fully qualified names for views that are closely related to the
    703      * given view
    704      *
    705      * @param fqcn the fully qualified class name
    706      * @return a list, never null but possibly empty, of views that are related to the
    707      *         view of the given type
    708      */
    709     public List<String> getRelatedTo(String fqcn) {
    710         ViewData view = getClassToView().get(fqcn);
    711         if (view != null) {
    712             return view.getRelatedTo();
    713         }
    714 
    715         return Collections.emptyList();
    716     }
    717 
    718     /** Render mode for palette preview */
    719     public enum RenderMode {
    720         /**
    721          * Render previews, and it can be rendered as a sibling of many other views in a
    722          * big linear layout
    723          */
    724         NORMAL,
    725         /** This view needs to be rendered alone */
    726         ALONE,
    727         /**
    728          * Skip this element; it doesn't work or does not produce any visible artifacts
    729          * (such as the basic layouts)
    730          */
    731         SKIP;
    732 
    733         /**
    734          * Returns the {@link RenderMode} for the given render XML attribute
    735          * value
    736          *
    737          * @param render the attribute value in the metadata XML file
    738          * @return a corresponding {@link RenderMode}, never null
    739          */
    740         public static RenderMode get(String render) {
    741             if ("alone".equals(render)) {       //$NON-NLS-1$
    742                 return ALONE;
    743             } else if ("skip".equals(render)) { //$NON-NLS-1$
    744                 return SKIP;
    745             } else {
    746                 return NORMAL;
    747             }
    748         }
    749     }
    750 
    751     /**
    752      * Are insets supported yet? This flag indicates whether the {@link #getInsets} method
    753      * can return valid data, such that clients can avoid doing any work computing the
    754      * current theme or density if there's no chance that valid insets will be returned
    755      */
    756     public static final boolean INSETS_SUPPORTED = false;
    757 
    758     /**
    759      * Returns the insets of widgets with the given fully qualified name, in the given
    760      * theme and the given screen density.
    761      *
    762      * @param fqcn the fully qualified name of the view
    763      * @param density the screen density
    764      * @param theme the theme name
    765      * @return the insets of the visual bounds relative to the view info bounds, or null
    766      *         if not known or if there are no insets
    767      */
    768     public static Margins getInsets(String fqcn, Density density, String theme) {
    769         if (INSETS_SUPPORTED) {
    770             // Some sample data measured manually for common themes and widgets.
    771             if (fqcn.equals(FQCN_BUTTON)) {
    772                 if (density == Density.HIGH) {
    773                     if (theme.startsWith(HOLO_PREFIX)) {
    774                         // Theme.Holo, Theme.Holo.Light, WVGA
    775                         return new Margins(5, 5, 5, 5);
    776                     } else {
    777                         // Theme.Light, WVGA
    778                         return new Margins(4, 4, 0, 7);
    779                     }
    780                 } else if (density == Density.MEDIUM) {
    781                     if (theme.startsWith(HOLO_PREFIX)) {
    782                         // Theme.Holo, Theme.Holo.Light, WVGA
    783                         return new Margins(3, 3, 3, 3);
    784                     } else {
    785                         // Theme.Light, HVGA
    786                         return new Margins(2, 2, 0, 4);
    787                     }
    788                 } else if (density == Density.LOW) {
    789                     if (theme.startsWith(HOLO_PREFIX)) {
    790                         // Theme.Holo, Theme.Holo.Light, QVGA
    791                         return new Margins(2, 2, 2, 2);
    792                     } else {
    793                         // Theme.Light, QVGA
    794                         return new Margins(1, 3, 0, 4);
    795                     }
    796                 }
    797             } else if (fqcn.equals(FQCN_TOGGLE_BUTTON)) {
    798                 if (density == Density.HIGH) {
    799                     if (theme.startsWith(HOLO_PREFIX)) {
    800                         // Theme.Holo, Theme.Holo.Light, WVGA
    801                         return new Margins(5, 5, 5, 5);
    802                     } else {
    803                         // Theme.Light, WVGA
    804                         return new Margins(2, 2, 0, 5);
    805                     }
    806                 } else if (density == Density.MEDIUM) {
    807                     if (theme.startsWith(HOLO_PREFIX)) {
    808                         // Theme.Holo, Theme.Holo.Light, WVGA
    809                         return new Margins(3, 3, 3, 3);
    810                     } else {
    811                         // Theme.Light, HVGA
    812                         return new Margins(0, 1, 0, 3);
    813                     }
    814                 } else if (density == Density.LOW) {
    815                     if (theme.startsWith(HOLO_PREFIX)) {
    816                         // Theme.Holo, Theme.Holo.Light, QVGA
    817                         return new Margins(2, 2, 2, 2);
    818                     } else {
    819                         // Theme.Light, QVGA
    820                         return new Margins(2, 2, 0, 4);
    821                     }
    822                 }
    823             } else if (fqcn.equals(FQCN_SPINNER)) {
    824                 if (density == Density.HIGH) {
    825                     if (!theme.startsWith(HOLO_PREFIX)) {
    826                         // Theme.Light, WVGA
    827                         return new Margins(3, 4, 2, 8);
    828                     } // Doesn't render on Holo!
    829                 } else if (density == Density.MEDIUM) {
    830                     if (!theme.startsWith(HOLO_PREFIX)) {
    831                         // Theme.Light, HVGA
    832                         return new Margins(1, 1, 0, 4);
    833                     }
    834                 }
    835             }
    836         }
    837 
    838         return null;
    839     }
    840 
    841     private static final String HOLO_PREFIX = "Theme.Holo"; //$NON-NLS-1$
    842 }
    843