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