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