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