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