• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
SimpleElement(String fqcn, String parentFqcn, Rect bounds, Rect parentBounds)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
getFqcn()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
getBounds()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
getParentFqcn()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
getParentBounds()101     public Rect getParentBounds() {
102         return mParentBounds;
103     }
104 
105     @Override
getAttributes()106     public IDragAttribute[] getAttributes() {
107         if (mCachedAttributes == null) {
108             mCachedAttributes = mAttributes.toArray(new IDragAttribute[mAttributes.size()]);
109         }
110         return mCachedAttributes;
111     }
112 
113     @Override
getAttribute(String uri, String localName)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
getInnerElements()125     public IDragElement[] getInnerElements() {
126         if (mCachedElements == null) {
127             mCachedElements = mElements.toArray(new IDragElement[mElements.size()]);
128         }
129         return mCachedElements;
130     }
131 
addAttribute(SimpleAttribute attr)132     public void addAttribute(SimpleAttribute attr) {
133         mCachedAttributes = null;
134         mAttributes.add(attr);
135     }
136 
addInnerElement(SimpleElement e)137     public void addInnerElement(SimpleElement e) {
138         mCachedElements = null;
139         mElements.add(e);
140     }
141 
142     // reader and writer methods
143 
144     @Override
toString()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. */
parseString(String value)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      */
parseLines(String[] lines, int[] inOutIndex)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
equals(Object obj)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
hashCode()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