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