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