Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2009 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.gle2;
     18 
     19 import com.android.annotations.NonNull;
     20 import com.android.annotations.Nullable;
     21 import com.android.ide.common.api.IDragElement;
     22 import com.android.ide.common.api.INode;
     23 import com.android.ide.common.api.Rect;
     24 
     25 import java.util.ArrayList;
     26 import java.util.List;
     27 
     28 /**
     29  * Represents an XML element with a name, attributes and inner elements.
     30  * <p/>
     31  * The semantic of the element name is to be a fully qualified class name of a View to inflate.
     32  * The element name is not expected to have a name space.
     33  * <p/>
     34  * For a more detailed explanation of the purpose of this class,
     35  * please see {@link SimpleXmlTransfer}.
     36  */
     37 public class SimpleElement implements IDragElement {
     38 
     39     /** Version number of the internal serialized string format. */
     40     private static final String FORMAT_VERSION = "3";
     41 
     42     private final String mFqcn;
     43     private final String mParentFqcn;
     44     private final Rect mBounds;
     45     private final Rect mParentBounds;
     46     private final List<IDragAttribute> mAttributes = new ArrayList<IDragAttribute>();
     47     private final List<IDragElement> mElements = new ArrayList<IDragElement>();
     48 
     49     private IDragAttribute[] mCachedAttributes = null;
     50     private IDragElement[] mCachedElements = null;
     51     private SelectionItem mSelectionItem;
     52 
     53     /**
     54      * Creates a new {@link SimpleElement} with the specified element name.
     55      *
     56      * @param fqcn A fully qualified class name of a View to inflate, e.g.
     57      *             "android.view.Button". Must not be null nor empty.
     58      * @param parentFqcn The fully qualified class name of the parent of this element.
     59      *                   Can be null but not empty.
     60      * @param bounds The canvas bounds of the originating canvas node of the element.
     61      *               If null, a non-null invalid rectangle will be assigned.
     62      * @param parentBounds The canvas bounds of the parent of this element. Can be null.
     63      */
     64     public SimpleElement(String fqcn, String parentFqcn, Rect bounds, Rect parentBounds) {
     65         mFqcn = fqcn;
     66         mParentFqcn = parentFqcn;
     67         mBounds = bounds == null ? new Rect() : bounds.copy();
     68         mParentBounds = parentBounds == null ? new Rect() : parentBounds.copy();
     69     }
     70 
     71     /**
     72      * Returns the element name, which must match a fully qualified class name of
     73      * a View to inflate.
     74      */
     75     @Override
     76     public @NonNull String getFqcn() {
     77         return mFqcn;
     78     }
     79 
     80     /**
     81      * Returns the bounds of the element's node, if it originated from an existing
     82      * canvas. The rectangle is invalid and non-null when the element originated
     83      * from the object palette (unless it successfully rendered a preview)
     84      */
     85     @Override
     86     public @NonNull Rect getBounds() {
     87         return mBounds;
     88     }
     89 
     90     /**
     91      * Returns the fully qualified class name of the parent, if the element originated
     92      * from an existing canvas. Returns null if the element has no parent, such as a top
     93      * level element or an element originating from the object palette.
     94      */
     95     @Override
     96     public String getParentFqcn() {
     97         return mParentFqcn;
     98     }
     99 
    100     /**
    101      * Returns the bounds of the element's parent, absolute for the canvas, or null if there
    102      * is no suitable parent. This is null when {@link #getParentFqcn()} is null.
    103      */
    104     @Override
    105     public @NonNull Rect getParentBounds() {
    106         return mParentBounds;
    107     }
    108 
    109     @Override
    110     public @NonNull IDragAttribute[] getAttributes() {
    111         if (mCachedAttributes == null) {
    112             mCachedAttributes = mAttributes.toArray(new IDragAttribute[mAttributes.size()]);
    113         }
    114         return mCachedAttributes;
    115     }
    116 
    117     @Override
    118     public IDragAttribute getAttribute(@Nullable String uri, @NonNull String localName) {
    119         for (IDragAttribute attr : mAttributes) {
    120             if (attr.getUri().equals(uri) && attr.getName().equals(localName)) {
    121                 return attr;
    122             }
    123         }
    124 
    125         return null;
    126     }
    127 
    128     @Override
    129     public @NonNull IDragElement[] getInnerElements() {
    130         if (mCachedElements == null) {
    131             mCachedElements = mElements.toArray(new IDragElement[mElements.size()]);
    132         }
    133         return mCachedElements;
    134     }
    135 
    136     public void addAttribute(SimpleAttribute attr) {
    137         mCachedAttributes = null;
    138         mAttributes.add(attr);
    139     }
    140 
    141     public void addInnerElement(SimpleElement e) {
    142         mCachedElements = null;
    143         mElements.add(e);
    144     }
    145 
    146     @Override
    147     public boolean isSame(@NonNull INode node) {
    148         if (mSelectionItem != null) {
    149             return node == mSelectionItem.getNode();
    150         } else {
    151             return node.getBounds().equals(mBounds);
    152         }
    153     }
    154 
    155     void setSelectionItem(@Nullable SelectionItem selectionItem) {
    156         mSelectionItem = selectionItem;
    157     }
    158 
    159     @Nullable
    160     SelectionItem getSelectionItem() {
    161         return mSelectionItem;
    162     }
    163 
    164     @Nullable
    165     static SimpleElement findPrimary(SimpleElement[] elements, SelectionItem primary) {
    166         if (elements == null || elements.length == 0) {
    167             return null;
    168         }
    169 
    170         if (elements.length == 1 || primary == null) {
    171             return elements[0];
    172         }
    173 
    174         for (SimpleElement element : elements) {
    175             if (element.getSelectionItem() == primary) {
    176                 return element;
    177             }
    178         }
    179 
    180         return elements[0];
    181     }
    182 
    183     // reader and writer methods
    184 
    185     @Override
    186     public String toString() {
    187         StringBuilder sb = new StringBuilder();
    188         sb.append("{V=").append(FORMAT_VERSION);
    189         sb.append(",N=").append(mFqcn);
    190         if (mParentFqcn != null) {
    191             sb.append(",P=").append(mParentFqcn);
    192         }
    193         if (mBounds != null && mBounds.isValid()) {
    194             sb.append(String.format(",R=%d %d %d %d", mBounds.x, mBounds.y, mBounds.w, mBounds.h));
    195         }
    196         if (mParentBounds != null && mParentBounds.isValid()) {
    197             sb.append(String.format(",Q=%d %d %d %d",
    198                     mParentBounds.x, mParentBounds.y, mParentBounds.w, mParentBounds.h));
    199         }
    200         sb.append('\n');
    201         for (IDragAttribute a : mAttributes) {
    202             sb.append(a.toString());
    203         }
    204         for (IDragElement e : mElements) {
    205             sb.append(e.toString());
    206         }
    207         sb.append("}\n"); //$NON-NLS-1$
    208         return sb.toString();
    209     }
    210 
    211     /** Parses a string containing one or more elements. */
    212     static SimpleElement[] parseString(String value) {
    213         ArrayList<SimpleElement> elements = new ArrayList<SimpleElement>();
    214         String[] lines = value.split("\n");
    215         int[] index = new int[] { 0 };
    216         SimpleElement element = null;
    217         while ((element = parseLines(lines, index)) != null) {
    218             elements.add(element);
    219         }
    220         return elements.toArray(new SimpleElement[elements.size()]);
    221     }
    222 
    223     /**
    224      * Parses one element from the input lines array, starting at the inOutIndex
    225      * and updating the inOutIndex to match the next unread line on output.
    226      */
    227     private static SimpleElement parseLines(String[] lines, int[] inOutIndex) {
    228         SimpleElement e = null;
    229         int index = inOutIndex[0];
    230         while (index < lines.length) {
    231             String line = lines[index++];
    232             String s = line.trim();
    233             if (s.startsWith("{")) {                                //$NON-NLS-1$
    234                 if (e == null) {
    235                     // This is the element's header, it should have
    236                     // the format "key=value,key=value,..."
    237                     String version = null;
    238                     String fqcn = null;
    239                     String parent = null;
    240                     Rect bounds = null;
    241                     Rect pbounds = null;
    242 
    243                     for (String s2 : s.substring(1).split(",")) {   //$NON-NLS-1$
    244                         int pos = s2.indexOf('=');
    245                         if (pos <= 0 || pos == s2.length() - 1) {
    246                             continue;
    247                         }
    248                         String key = s2.substring(0, pos).trim();
    249                         String value = s2.substring(pos + 1).trim();
    250 
    251                         if (key.equals("V")) {                      //$NON-NLS-1$
    252                             version = value;
    253                             if (!value.equals(FORMAT_VERSION)) {
    254                                 // Wrong format version. Don't even try to process anything
    255                                 // else and just give up everything.
    256                                 inOutIndex[0] = index;
    257                                 return null;
    258                             }
    259 
    260                         } else if (key.equals("N")) {               //$NON-NLS-1$
    261                             fqcn = value;
    262 
    263                         } else if (key.equals("P")) {               //$NON-NLS-1$
    264                             parent = value;
    265 
    266                         } else if (key.equals("R") || key.equals("Q")) { //$NON-NLS-1$ //$NON-NLS-2$
    267                             // Parse the canvas bounds
    268                             String[] sb = value.split(" +");        //$NON-NLS-1$
    269                             if (sb != null && sb.length == 4) {
    270                                 Rect r = null;
    271                                 try {
    272                                     r = new Rect();
    273                                     r.x = Integer.parseInt(sb[0]);
    274                                     r.y = Integer.parseInt(sb[1]);
    275                                     r.w = Integer.parseInt(sb[2]);
    276                                     r.h = Integer.parseInt(sb[3]);
    277 
    278                                     if (key.equals("R")) {
    279                                         bounds = r;
    280                                     } else {
    281                                         pbounds = r;
    282                                     }
    283                                 } catch (NumberFormatException ignore) {
    284                                 }
    285                             }
    286                         }
    287                     }
    288 
    289                     // We need at least a valid name to recreate an element
    290                     if (version != null && fqcn != null && fqcn.length() > 0) {
    291                         e = new SimpleElement(fqcn, parent, bounds, pbounds);
    292                     }
    293                 } else {
    294                     // This is an inner element... need to parse the { line again.
    295                     inOutIndex[0] = index - 1;
    296                     SimpleElement e2 = SimpleElement.parseLines(lines, inOutIndex);
    297                     if (e2 != null) {
    298                         e.addInnerElement(e2);
    299                     }
    300                     index = inOutIndex[0];
    301                 }
    302 
    303             } else if (e != null && s.startsWith("@")) {    //$NON-NLS-1$
    304                 SimpleAttribute a = SimpleAttribute.parseString(line);
    305                 if (a != null) {
    306                     e.addAttribute(a);
    307                 }
    308 
    309             } else if (e != null && s.startsWith("}")) {     //$NON-NLS-1$
    310                 // We're done with this element
    311                 inOutIndex[0] = index;
    312                 return e;
    313             }
    314         }
    315         inOutIndex[0] = index;
    316         return null;
    317     }
    318 
    319     @Override
    320     public boolean equals(Object obj) {
    321         if (obj instanceof SimpleElement) {
    322             SimpleElement se = (SimpleElement) obj;
    323 
    324             // Bounds and parentFqcn must be null on both sides or equal.
    325             if ((mBounds == null && se.mBounds != null) ||
    326                     (mBounds != null && !mBounds.equals(se.mBounds))) {
    327                 return false;
    328             }
    329             if ((mParentFqcn == null && se.mParentFqcn != null) ||
    330                     (mParentFqcn != null && !mParentFqcn.equals(se.mParentFqcn))) {
    331                 return false;
    332             }
    333             if ((mParentBounds == null && se.mParentBounds != null) ||
    334                     (mParentBounds != null && !mParentBounds.equals(se.mParentBounds))) {
    335                 return false;
    336             }
    337 
    338             return mFqcn.equals(se.mFqcn) &&
    339                     mAttributes.size() == se.mAttributes.size() &&
    340                     mElements.size() == se.mElements.size() &&
    341                     mAttributes.equals(se.mAttributes) &&
    342                     mElements.equals(se.mElements);
    343         }
    344         return false;
    345     }
    346 
    347     @Override
    348     public int hashCode() {
    349         long c = mFqcn.hashCode();
    350         // uses the formula defined in java.util.List.hashCode()
    351         c = 31*c + mAttributes.hashCode();
    352         c = 31*c + mElements.hashCode();
    353         if (mParentFqcn != null) {
    354             c = 31*c + mParentFqcn.hashCode();
    355         }
    356         if (mBounds != null && mBounds.isValid()) {
    357             c = 31*c + mBounds.hashCode();
    358         }
    359         if (mParentBounds != null && mParentBounds.isValid()) {
    360             c = 31*c + mParentBounds.hashCode();
    361         }
    362 
    363         if (c > 0x0FFFFFFFFL) {
    364             // wrap any overflow
    365             c = c ^ (c >> 32);
    366         }
    367         return (int)(c & 0x0FFFFFFFFL);
    368     }
    369 }
    370 
    371