1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 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 android.content.res; 18 19 import android.util.TypedValue; 20 21 import com.android.internal.util.XmlUtils; 22 23 import org.xmlpull.v1.XmlPullParserException; 24 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.io.Reader; 28 29 /** 30 * Wrapper around a compiled XML file. 31 * 32 * {@hide} 33 */ 34 final class XmlBlock { 35 private static final boolean DEBUG=false; 36 37 public XmlBlock(byte[] data) { 38 mAssets = null; 39 mNative = nativeCreate(data, 0, data.length); 40 mStrings = new StringBlock(nativeGetStringBlock(mNative), false); 41 } 42 43 public XmlBlock(byte[] data, int offset, int size) { 44 mAssets = null; 45 mNative = nativeCreate(data, offset, size); 46 mStrings = new StringBlock(nativeGetStringBlock(mNative), false); 47 } 48 49 public void close() { 50 synchronized (this) { 51 if (mOpen) { 52 mOpen = false; 53 decOpenCountLocked(); 54 } 55 } 56 } 57 58 private void decOpenCountLocked() { 59 mOpenCount--; 60 if (mOpenCount == 0) { 61 nativeDestroy(mNative); 62 if (mAssets != null) { 63 mAssets.xmlBlockGone(hashCode()); 64 } 65 } 66 } 67 68 public XmlResourceParser newParser() { 69 synchronized (this) { 70 if (mNative != 0) { 71 return new Parser(nativeCreateParseState(mNative), this); 72 } 73 return null; 74 } 75 } 76 77 /*package*/ final class Parser implements XmlResourceParser { 78 Parser(int parseState, XmlBlock block) { 79 mParseState = parseState; 80 mBlock = block; 81 block.mOpenCount++; 82 } 83 84 public void setFeature(String name, boolean state) throws XmlPullParserException { 85 if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) { 86 return; 87 } 88 if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) { 89 return; 90 } 91 throw new XmlPullParserException("Unsupported feature: " + name); 92 } 93 public boolean getFeature(String name) { 94 if (FEATURE_PROCESS_NAMESPACES.equals(name)) { 95 return true; 96 } 97 if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) { 98 return true; 99 } 100 return false; 101 } 102 public void setProperty(String name, Object value) throws XmlPullParserException { 103 throw new XmlPullParserException("setProperty() not supported"); 104 } 105 public Object getProperty(String name) { 106 return null; 107 } 108 public void setInput(Reader in) throws XmlPullParserException { 109 throw new XmlPullParserException("setInput() not supported"); 110 } 111 public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException { 112 throw new XmlPullParserException("setInput() not supported"); 113 } 114 public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException { 115 throw new XmlPullParserException("defineEntityReplacementText() not supported"); 116 } 117 public String getNamespacePrefix(int pos) throws XmlPullParserException { 118 throw new XmlPullParserException("getNamespacePrefix() not supported"); 119 } 120 public String getInputEncoding() { 121 return null; 122 } 123 public String getNamespace(String prefix) { 124 throw new RuntimeException("getNamespace() not supported"); 125 } 126 public int getNamespaceCount(int depth) throws XmlPullParserException { 127 throw new XmlPullParserException("getNamespaceCount() not supported"); 128 } 129 public String getPositionDescription() { 130 return "Binary XML file line #" + getLineNumber(); 131 } 132 public String getNamespaceUri(int pos) throws XmlPullParserException { 133 throw new XmlPullParserException("getNamespaceUri() not supported"); 134 } 135 public int getColumnNumber() { 136 return -1; 137 } 138 public int getDepth() { 139 return mDepth; 140 } 141 public String getText() { 142 int id = nativeGetText(mParseState); 143 return id >= 0 ? mStrings.get(id).toString() : null; 144 } 145 public int getLineNumber() { 146 return nativeGetLineNumber(mParseState); 147 } 148 public int getEventType() throws XmlPullParserException { 149 return mEventType; 150 } 151 public boolean isWhitespace() throws XmlPullParserException { 152 // whitespace was stripped by aapt. 153 return false; 154 } 155 public String getPrefix() { 156 throw new RuntimeException("getPrefix not supported"); 157 } 158 public char[] getTextCharacters(int[] holderForStartAndLength) { 159 String txt = getText(); 160 char[] chars = null; 161 if (txt != null) { 162 holderForStartAndLength[0] = 0; 163 holderForStartAndLength[1] = txt.length(); 164 chars = new char[txt.length()]; 165 txt.getChars(0, txt.length(), chars, 0); 166 } 167 return chars; 168 } 169 public String getNamespace() { 170 int id = nativeGetNamespace(mParseState); 171 return id >= 0 ? mStrings.get(id).toString() : ""; 172 } 173 public String getName() { 174 int id = nativeGetName(mParseState); 175 return id >= 0 ? mStrings.get(id).toString() : null; 176 } 177 public String getAttributeNamespace(int index) { 178 int id = nativeGetAttributeNamespace(mParseState, index); 179 if (DEBUG) System.out.println("getAttributeNamespace of " + index + " = " + id); 180 if (id >= 0) return mStrings.get(id).toString(); 181 else if (id == -1) return ""; 182 throw new IndexOutOfBoundsException(String.valueOf(index)); 183 } 184 public String getAttributeName(int index) { 185 int id = nativeGetAttributeName(mParseState, index); 186 if (DEBUG) System.out.println("getAttributeName of " + index + " = " + id); 187 if (id >= 0) return mStrings.get(id).toString(); 188 throw new IndexOutOfBoundsException(String.valueOf(index)); 189 } 190 public String getAttributePrefix(int index) { 191 throw new RuntimeException("getAttributePrefix not supported"); 192 } 193 public boolean isEmptyElementTag() throws XmlPullParserException { 194 // XXX Need to detect this. 195 return false; 196 } 197 public int getAttributeCount() { 198 return mEventType == START_TAG ? nativeGetAttributeCount(mParseState) : -1; 199 } 200 public String getAttributeValue(int index) { 201 int id = nativeGetAttributeStringValue(mParseState, index); 202 if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id); 203 if (id >= 0) return mStrings.get(id).toString(); 204 205 // May be some other type... check and try to convert if so. 206 int t = nativeGetAttributeDataType(mParseState, index); 207 if (t == TypedValue.TYPE_NULL) { 208 throw new IndexOutOfBoundsException(String.valueOf(index)); 209 } 210 211 int v = nativeGetAttributeData(mParseState, index); 212 return TypedValue.coerceToString(t, v); 213 } 214 public String getAttributeType(int index) { 215 return "CDATA"; 216 } 217 public boolean isAttributeDefault(int index) { 218 return false; 219 } 220 public int nextToken() throws XmlPullParserException,IOException { 221 return next(); 222 } 223 public String getAttributeValue(String namespace, String name) { 224 int idx = nativeGetAttributeIndex(mParseState, namespace, name); 225 if (idx >= 0) { 226 if (DEBUG) System.out.println("getAttributeName of " 227 + namespace + ":" + name + " index = " + idx); 228 if (DEBUG) System.out.println( 229 "Namespace=" + getAttributeNamespace(idx) 230 + "Name=" + getAttributeName(idx) 231 + ", Value=" + getAttributeValue(idx)); 232 return getAttributeValue(idx); 233 } 234 return null; 235 } 236 public int next() throws XmlPullParserException,IOException { 237 if (!mStarted) { 238 mStarted = true; 239 return START_DOCUMENT; 240 } 241 if (mParseState == 0) { 242 return END_DOCUMENT; 243 } 244 int ev = nativeNext(mParseState); 245 if (mDecNextDepth) { 246 mDepth--; 247 mDecNextDepth = false; 248 } 249 switch (ev) { 250 case START_TAG: 251 mDepth++; 252 break; 253 case END_TAG: 254 mDecNextDepth = true; 255 break; 256 } 257 mEventType = ev; 258 if (ev == END_DOCUMENT) { 259 // Automatically close the parse when we reach the end of 260 // a document, since the standard XmlPullParser interface 261 // doesn't have such an API so most clients will leave us 262 // dangling. 263 close(); 264 } 265 return ev; 266 } 267 public void require(int type, String namespace, String name) throws XmlPullParserException,IOException { 268 if (type != getEventType() 269 || (namespace != null && !namespace.equals( getNamespace () ) ) 270 || (name != null && !name.equals( getName() ) ) ) 271 throw new XmlPullParserException( "expected "+ TYPES[ type ]+getPositionDescription()); 272 } 273 public String nextText() throws XmlPullParserException,IOException { 274 if(getEventType() != START_TAG) { 275 throw new XmlPullParserException( 276 getPositionDescription() 277 + ": parser must be on START_TAG to read next text", this, null); 278 } 279 int eventType = next(); 280 if(eventType == TEXT) { 281 String result = getText(); 282 eventType = next(); 283 if(eventType != END_TAG) { 284 throw new XmlPullParserException( 285 getPositionDescription() 286 + ": event TEXT it must be immediately followed by END_TAG", this, null); 287 } 288 return result; 289 } else if(eventType == END_TAG) { 290 return ""; 291 } else { 292 throw new XmlPullParserException( 293 getPositionDescription() 294 + ": parser must be on START_TAG or TEXT to read text", this, null); 295 } 296 } 297 public int nextTag() throws XmlPullParserException,IOException { 298 int eventType = next(); 299 if(eventType == TEXT && isWhitespace()) { // skip whitespace 300 eventType = next(); 301 } 302 if (eventType != START_TAG && eventType != END_TAG) { 303 throw new XmlPullParserException( 304 getPositionDescription() 305 + ": expected start or end tag", this, null); 306 } 307 return eventType; 308 } 309 310 public int getAttributeNameResource(int index) { 311 return nativeGetAttributeResource(mParseState, index); 312 } 313 314 public int getAttributeListValue(String namespace, String attribute, 315 String[] options, int defaultValue) { 316 int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); 317 if (idx >= 0) { 318 return getAttributeListValue(idx, options, defaultValue); 319 } 320 return defaultValue; 321 } 322 public boolean getAttributeBooleanValue(String namespace, String attribute, 323 boolean defaultValue) { 324 int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); 325 if (idx >= 0) { 326 return getAttributeBooleanValue(idx, defaultValue); 327 } 328 return defaultValue; 329 } 330 public int getAttributeResourceValue(String namespace, String attribute, 331 int defaultValue) { 332 int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); 333 if (idx >= 0) { 334 return getAttributeResourceValue(idx, defaultValue); 335 } 336 return defaultValue; 337 } 338 public int getAttributeIntValue(String namespace, String attribute, 339 int defaultValue) { 340 int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); 341 if (idx >= 0) { 342 return getAttributeIntValue(idx, defaultValue); 343 } 344 return defaultValue; 345 } 346 public int getAttributeUnsignedIntValue(String namespace, String attribute, 347 int defaultValue) 348 { 349 int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); 350 if (idx >= 0) { 351 return getAttributeUnsignedIntValue(idx, defaultValue); 352 } 353 return defaultValue; 354 } 355 public float getAttributeFloatValue(String namespace, String attribute, 356 float defaultValue) { 357 int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); 358 if (idx >= 0) { 359 return getAttributeFloatValue(idx, defaultValue); 360 } 361 return defaultValue; 362 } 363 364 public int getAttributeListValue(int idx, 365 String[] options, int defaultValue) { 366 int t = nativeGetAttributeDataType(mParseState, idx); 367 int v = nativeGetAttributeData(mParseState, idx); 368 if (t == TypedValue.TYPE_STRING) { 369 return XmlUtils.convertValueToList( 370 mStrings.get(v), options, defaultValue); 371 } 372 return v; 373 } 374 public boolean getAttributeBooleanValue(int idx, 375 boolean defaultValue) { 376 int t = nativeGetAttributeDataType(mParseState, idx); 377 // Note: don't attempt to convert any other types, because 378 // we want to count on appt doing the conversion for us. 379 if (t >= TypedValue.TYPE_FIRST_INT && 380 t <= TypedValue.TYPE_LAST_INT) { 381 return nativeGetAttributeData(mParseState, idx) != 0; 382 } 383 return defaultValue; 384 } 385 public int getAttributeResourceValue(int idx, int defaultValue) { 386 int t = nativeGetAttributeDataType(mParseState, idx); 387 // Note: don't attempt to convert any other types, because 388 // we want to count on appt doing the conversion for us. 389 if (t == TypedValue.TYPE_REFERENCE) { 390 return nativeGetAttributeData(mParseState, idx); 391 } 392 return defaultValue; 393 } 394 public int getAttributeIntValue(int idx, int defaultValue) { 395 int t = nativeGetAttributeDataType(mParseState, idx); 396 // Note: don't attempt to convert any other types, because 397 // we want to count on appt doing the conversion for us. 398 if (t >= TypedValue.TYPE_FIRST_INT && 399 t <= TypedValue.TYPE_LAST_INT) { 400 return nativeGetAttributeData(mParseState, idx); 401 } 402 return defaultValue; 403 } 404 public int getAttributeUnsignedIntValue(int idx, int defaultValue) { 405 int t = nativeGetAttributeDataType(mParseState, idx); 406 // Note: don't attempt to convert any other types, because 407 // we want to count on appt doing the conversion for us. 408 if (t >= TypedValue.TYPE_FIRST_INT && 409 t <= TypedValue.TYPE_LAST_INT) { 410 return nativeGetAttributeData(mParseState, idx); 411 } 412 return defaultValue; 413 } 414 public float getAttributeFloatValue(int idx, float defaultValue) { 415 int t = nativeGetAttributeDataType(mParseState, idx); 416 // Note: don't attempt to convert any other types, because 417 // we want to count on appt doing the conversion for us. 418 if (t == TypedValue.TYPE_FLOAT) { 419 return Float.intBitsToFloat( 420 nativeGetAttributeData(mParseState, idx)); 421 } 422 throw new RuntimeException("not a float!"); 423 } 424 425 public String getIdAttribute() { 426 int id = nativeGetIdAttribute(mParseState); 427 return id >= 0 ? mStrings.get(id).toString() : null; 428 } 429 public String getClassAttribute() { 430 int id = nativeGetClassAttribute(mParseState); 431 return id >= 0 ? mStrings.get(id).toString() : null; 432 } 433 434 public int getIdAttributeResourceValue(int defaultValue) { 435 //todo: create and use native method 436 return getAttributeResourceValue(null, "id", defaultValue); 437 } 438 439 public int getStyleAttribute() { 440 return nativeGetStyleAttribute(mParseState); 441 } 442 443 public void close() { 444 synchronized (mBlock) { 445 if (mParseState != 0) { 446 nativeDestroyParseState(mParseState); 447 mParseState = 0; 448 mBlock.decOpenCountLocked(); 449 } 450 } 451 } 452 453 protected void finalize() throws Throwable { 454 close(); 455 } 456 457 /*package*/ final CharSequence getPooledString(int id) { 458 return mStrings.get(id); 459 } 460 461 /*package*/ int mParseState; 462 private final XmlBlock mBlock; 463 private boolean mStarted = false; 464 private boolean mDecNextDepth = false; 465 private int mDepth = 0; 466 private int mEventType = START_DOCUMENT; 467 } 468 469 protected void finalize() throws Throwable { 470 close(); 471 } 472 473 /** 474 * Create from an existing xml block native object. This is 475 * -extremely- dangerous -- only use it if you absolutely know what you 476 * are doing! The given native object must exist for the entire lifetime 477 * of this newly creating XmlBlock. 478 */ 479 XmlBlock(AssetManager assets, int xmlBlock) { 480 mAssets = assets; 481 mNative = xmlBlock; 482 mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false); 483 } 484 485 private final AssetManager mAssets; 486 private final int mNative; 487 private final StringBlock mStrings; 488 private boolean mOpen = true; 489 private int mOpenCount = 1; 490 491 private static final native int nativeCreate(byte[] data, 492 int offset, 493 int size); 494 private static final native int nativeGetStringBlock(int obj); 495 496 private static final native int nativeCreateParseState(int obj); 497 private static final native int nativeNext(int state); 498 private static final native int nativeGetNamespace(int state); 499 private static final native int nativeGetName(int state); 500 private static final native int nativeGetText(int state); 501 private static final native int nativeGetLineNumber(int state); 502 private static final native int nativeGetAttributeCount(int state); 503 private static final native int nativeGetAttributeNamespace(int state, int idx); 504 private static final native int nativeGetAttributeName(int state, int idx); 505 private static final native int nativeGetAttributeResource(int state, int idx); 506 private static final native int nativeGetAttributeDataType(int state, int idx); 507 private static final native int nativeGetAttributeData(int state, int idx); 508 private static final native int nativeGetAttributeStringValue(int state, int idx); 509 private static final native int nativeGetIdAttribute(int state); 510 private static final native int nativeGetClassAttribute(int state); 511 private static final native int nativeGetStyleAttribute(int state); 512 private static final native int nativeGetAttributeIndex(int state, String namespace, String name); 513 private static final native void nativeDestroyParseState(int state); 514 515 private static final native void nativeDestroy(int obj); 516 } 517