blob: bed5a2e219aefa2a68cb052fdc63b1cba7bb0a40 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package com.android.internal.util;
18
Jeff Sharkey59d577a2015-04-11 21:27:21 -070019import android.annotation.NonNull;
20import android.annotation.Nullable;
Jeff Sharkeyda96e132014-07-15 14:54:09 -070021import android.util.ArraySet;
22
Adam Lesinski776abc22014-03-07 11:30:59 -050023import dalvik.system.VMRuntime;
Jeff Sharkeyda96e132014-07-15 14:54:09 -070024
Adam Lesinski776abc22014-03-07 11:30:59 -050025import libcore.util.EmptyArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026
Adam Lesinski776abc22014-03-07 11:30:59 -050027import java.lang.reflect.Array;
Jeff Sharkeyda96e132014-07-15 14:54:09 -070028import java.util.ArrayList;
Jeff Sharkey2bd31db2016-01-09 16:58:14 -070029import java.util.Arrays;
Adam Lesinski082614c2016-03-04 14:33:47 -080030import java.util.Collections;
Jeff Sharkey3e195892016-03-05 19:48:59 -070031import java.util.List;
Jeff Sharkey59d577a2015-04-11 21:27:21 -070032import java.util.Objects;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
34/**
35 * ArrayUtils contains some methods that you can call to find out
36 * the most efficient increments by which to grow arrays.
37 */
Jeff Sharkey59d577a2015-04-11 21:27:21 -070038public class ArrayUtils {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039 private static final int CACHE_SIZE = 73;
40 private static Object[] sCache = new Object[CACHE_SIZE];
41
42 private ArrayUtils() { /* cannot be instantiated */ }
43
Adam Lesinski776abc22014-03-07 11:30:59 -050044 public static byte[] newUnpaddedByteArray(int minLen) {
45 return (byte[])VMRuntime.getRuntime().newUnpaddedArray(byte.class, minLen);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 }
47
Adam Lesinski776abc22014-03-07 11:30:59 -050048 public static char[] newUnpaddedCharArray(int minLen) {
49 return (char[])VMRuntime.getRuntime().newUnpaddedArray(char.class, minLen);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 }
51
Adam Lesinski776abc22014-03-07 11:30:59 -050052 public static int[] newUnpaddedIntArray(int minLen) {
53 return (int[])VMRuntime.getRuntime().newUnpaddedArray(int.class, minLen);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054 }
55
Adam Lesinski776abc22014-03-07 11:30:59 -050056 public static boolean[] newUnpaddedBooleanArray(int minLen) {
57 return (boolean[])VMRuntime.getRuntime().newUnpaddedArray(boolean.class, minLen);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058 }
59
Adam Lesinski776abc22014-03-07 11:30:59 -050060 public static long[] newUnpaddedLongArray(int minLen) {
61 return (long[])VMRuntime.getRuntime().newUnpaddedArray(long.class, minLen);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 }
63
Adam Lesinski776abc22014-03-07 11:30:59 -050064 public static float[] newUnpaddedFloatArray(int minLen) {
65 return (float[])VMRuntime.getRuntime().newUnpaddedArray(float.class, minLen);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066 }
67
Adam Lesinski776abc22014-03-07 11:30:59 -050068 public static Object[] newUnpaddedObjectArray(int minLen) {
69 return (Object[])VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070 }
71
Adam Lesinski776abc22014-03-07 11:30:59 -050072 @SuppressWarnings("unchecked")
73 public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
74 return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 }
76
77 /**
78 * Checks if the beginnings of two byte arrays are equal.
79 *
80 * @param array1 the first byte array
81 * @param array2 the second byte array
82 * @param length the number of bytes to check
83 * @return true if they're equal, false otherwise
84 */
85 public static boolean equals(byte[] array1, byte[] array2, int length) {
Christopher Tatefc054342013-03-22 15:01:13 -070086 if (length < 0) {
87 throw new IllegalArgumentException();
88 }
89
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 if (array1 == array2) {
91 return true;
92 }
93 if (array1 == null || array2 == null || array1.length < length || array2.length < length) {
94 return false;
95 }
96 for (int i = 0; i < length; i++) {
97 if (array1[i] != array2[i]) {
98 return false;
99 }
100 }
101 return true;
102 }
103
104 /**
105 * Returns an empty array of the specified type. The intent is that
106 * it will return the same empty array every time to avoid reallocation,
107 * although this is not guaranteed.
108 */
Adam Lesinski776abc22014-03-07 11:30:59 -0500109 @SuppressWarnings("unchecked")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 public static <T> T[] emptyArray(Class<T> kind) {
111 if (kind == Object.class) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500112 return (T[]) EmptyArray.OBJECT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 }
114
Mathieu Chartierb4e50612014-09-10 12:56:21 -0700115 int bucket = (kind.hashCode() & 0x7FFFFFFF) % CACHE_SIZE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 Object cache = sCache[bucket];
117
118 if (cache == null || cache.getClass().getComponentType() != kind) {
119 cache = Array.newInstance(kind, 0);
120 sCache[bucket] = cache;
121
122 // Log.e("cache", "new empty " + kind.getName() + " at " + bucket);
123 }
124
125 return (T[]) cache;
126 }
127
128 /**
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700129 * Checks if given array is null or has zero elements.
130 */
Jeff Sharkey3e195892016-03-05 19:48:59 -0700131 public static boolean isEmpty(@Nullable List<?> array) {
132 return array == null || array.isEmpty();
133 }
134
135 /**
136 * Checks if given array is null or has zero elements.
137 */
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700138 public static <T> boolean isEmpty(@Nullable T[] array) {
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700139 return array == null || array.length == 0;
140 }
141
142 /**
Jeff Sharkey32566012014-12-02 18:30:14 -0800143 * Checks if given array is null or has zero elements.
144 */
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700145 public static boolean isEmpty(@Nullable int[] array) {
Jeff Sharkey32566012014-12-02 18:30:14 -0800146 return array == null || array.length == 0;
147 }
148
149 /**
150 * Checks if given array is null or has zero elements.
151 */
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700152 public static boolean isEmpty(@Nullable long[] array) {
Jeff Sharkey32566012014-12-02 18:30:14 -0800153 return array == null || array.length == 0;
154 }
155
156 /**
Jeff Sharkeyf9fc6d62015-11-08 16:46:05 -0800157 * Checks if given array is null or has zero elements.
158 */
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700159 public static boolean isEmpty(@Nullable byte[] array) {
Jeff Sharkeyf9fc6d62015-11-08 16:46:05 -0800160 return array == null || array.length == 0;
161 }
162
163 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 * Checks that value is present as at least one of the elements of the array.
165 * @param array the array to check in
166 * @param value the value to check for
167 * @return true if the value is present in the array
168 */
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700169 public static <T> boolean contains(@Nullable T[] array, T value) {
Jeff Sharkey94c91dc2013-03-06 16:25:50 -0800170 return indexOf(array, value) != -1;
171 }
172
173 /**
174 * Return first index of {@code value} in {@code array}, or {@code -1} if
175 * not found.
176 */
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700177 public static <T> int indexOf(@Nullable T[] array, T value) {
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700178 if (array == null) return -1;
Jeff Sharkey94c91dc2013-03-06 16:25:50 -0800179 for (int i = 0; i < array.length; i++) {
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700180 if (Objects.equals(array[i], value)) return i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
Jeff Sharkey94c91dc2013-03-06 16:25:50 -0800182 return -1;
183 }
184
185 /**
186 * Test if all {@code check} items are contained in {@code array}.
187 */
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700188 public static <T> boolean containsAll(@Nullable T[] array, T[] check) {
Jeff Sharkey32566012014-12-02 18:30:14 -0800189 if (check == null) return true;
Jeff Sharkey94c91dc2013-03-06 16:25:50 -0800190 for (T checkItem : check) {
191 if (!contains(array, checkItem)) {
192 return false;
193 }
194 }
195 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196 }
Jeff Sharkey630a1712011-09-26 10:47:10 -0700197
Jeff Sharkey35871f22016-01-29 17:13:29 -0700198 /**
199 * Test if any {@code check} items are contained in {@code array}.
200 */
201 public static <T> boolean containsAny(@Nullable T[] array, T[] check) {
202 if (check == null) return false;
203 for (T checkItem : check) {
204 if (contains(array, checkItem)) {
205 return true;
206 }
207 }
208 return false;
209 }
210
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700211 public static boolean contains(@Nullable int[] array, int value) {
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700212 if (array == null) return false;
Jeff Sharkey630a1712011-09-26 10:47:10 -0700213 for (int element : array) {
214 if (element == value) {
215 return true;
216 }
217 }
218 return false;
219 }
Jeff Brown96e942d2011-11-30 19:55:01 -0800220
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700221 public static boolean contains(@Nullable long[] array, long value) {
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700222 if (array == null) return false;
dcashman874d0d42014-05-30 09:34:04 -0700223 for (long element : array) {
224 if (element == value) {
225 return true;
226 }
227 }
228 return false;
229 }
230
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700231 public static long total(@Nullable long[] array) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800232 long total = 0;
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700233 if (array != null) {
234 for (long value : array) {
235 total += value;
236 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800237 }
238 return total;
239 }
240
Chong Zhang167bbfac2016-03-31 09:44:34 -0700241 public static int[] convertToIntArray(List<Integer> list) {
242 int[] array = new int[list.size()];
243 for (int i = 0; i < list.size(); i++) {
244 array[i] = list.get(i);
245 }
246 return array;
247 }
248
Jeff Brown96e942d2011-11-30 19:55:01 -0800249 /**
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700250 * Adds value to given array if not already present, providing set-like
251 * behavior.
Jeff Brown96e942d2011-11-30 19:55:01 -0800252 */
253 @SuppressWarnings("unchecked")
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700254 public static @NonNull <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element) {
Jeff Brown96e942d2011-11-30 19:55:01 -0800255 final T[] result;
256 final int end;
257 if (array != null) {
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700258 if (contains(array, element)) return array;
Jeff Brown96e942d2011-11-30 19:55:01 -0800259 end = array.length;
260 result = (T[])Array.newInstance(kind, end + 1);
261 System.arraycopy(array, 0, result, 0, end);
262 } else {
263 end = 0;
264 result = (T[])Array.newInstance(kind, 1);
265 }
266 result[end] = element;
267 return result;
268 }
269
270 /**
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700271 * Removes value from given array if present, providing set-like behavior.
Jeff Brown96e942d2011-11-30 19:55:01 -0800272 */
273 @SuppressWarnings("unchecked")
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700274 public static @Nullable <T> T[] removeElement(Class<T> kind, @Nullable T[] array, T element) {
Jeff Brown96e942d2011-11-30 19:55:01 -0800275 if (array != null) {
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700276 if (!contains(array, element)) return array;
Jeff Brown96e942d2011-11-30 19:55:01 -0800277 final int length = array.length;
278 for (int i = 0; i < length; i++) {
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700279 if (Objects.equals(array[i], element)) {
Jeff Brown96e942d2011-11-30 19:55:01 -0800280 if (length == 1) {
281 return null;
282 }
283 T[] result = (T[])Array.newInstance(kind, length - 1);
284 System.arraycopy(array, 0, result, 0, i);
285 System.arraycopy(array, i + 1, result, i, length - i - 1);
286 return result;
287 }
288 }
289 }
290 return array;
291 }
Jeff Sharkey854b2b12012-04-13 16:03:40 -0700292
dcashman874d0d42014-05-30 09:34:04 -0700293 /**
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700294 * Adds value to given array if not already present, providing set-like
295 * behavior.
dcashman874d0d42014-05-30 09:34:04 -0700296 */
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700297 public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
Jeff Sharkey854b2b12012-04-13 16:03:40 -0700298 if (cur == null) {
299 return new int[] { val };
300 }
301 final int N = cur.length;
302 for (int i = 0; i < N; i++) {
303 if (cur[i] == val) {
304 return cur;
305 }
306 }
307 int[] ret = new int[N + 1];
308 System.arraycopy(cur, 0, ret, 0, N);
309 ret[N] = val;
310 return ret;
311 }
312
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700313 /**
314 * Removes value from given array if present, providing set-like behavior.
315 */
316 public static @Nullable int[] removeInt(@Nullable int[] cur, int val) {
Jeff Sharkey854b2b12012-04-13 16:03:40 -0700317 if (cur == null) {
318 return null;
319 }
320 final int N = cur.length;
321 for (int i = 0; i < N; i++) {
322 if (cur[i] == val) {
323 int[] ret = new int[N - 1];
324 if (i > 0) {
325 System.arraycopy(cur, 0, ret, 0, i);
326 }
327 if (i < (N - 1)) {
328 System.arraycopy(cur, i + 1, ret, i, N - i - 1);
329 }
330 return ret;
331 }
332 }
333 return cur;
334 }
dcashman874d0d42014-05-30 09:34:04 -0700335
336 /**
Svet Ganov52153f42015-08-11 08:59:12 -0700337 * Removes value from given array if present, providing set-like behavior.
338 */
339 public static @Nullable String[] removeString(@Nullable String[] cur, String val) {
340 if (cur == null) {
341 return null;
342 }
343 final int N = cur.length;
344 for (int i = 0; i < N; i++) {
345 if (Objects.equals(cur[i], val)) {
346 String[] ret = new String[N - 1];
347 if (i > 0) {
348 System.arraycopy(cur, 0, ret, 0, i);
349 }
350 if (i < (N - 1)) {
351 System.arraycopy(cur, i + 1, ret, i, N - i - 1);
352 }
353 return ret;
354 }
355 }
356 return cur;
357 }
358
359 /**
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700360 * Adds value to given array if not already present, providing set-like
361 * behavior.
dcashman874d0d42014-05-30 09:34:04 -0700362 */
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700363 public static @NonNull long[] appendLong(@Nullable long[] cur, long val) {
dcashman874d0d42014-05-30 09:34:04 -0700364 if (cur == null) {
365 return new long[] { val };
366 }
367 final int N = cur.length;
368 for (int i = 0; i < N; i++) {
369 if (cur[i] == val) {
370 return cur;
371 }
372 }
373 long[] ret = new long[N + 1];
374 System.arraycopy(cur, 0, ret, 0, N);
375 ret[N] = val;
376 return ret;
377 }
378
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700379 /**
380 * Removes value from given array if present, providing set-like behavior.
381 */
382 public static @Nullable long[] removeLong(@Nullable long[] cur, long val) {
dcashman874d0d42014-05-30 09:34:04 -0700383 if (cur == null) {
384 return null;
385 }
386 final int N = cur.length;
387 for (int i = 0; i < N; i++) {
388 if (cur[i] == val) {
389 long[] ret = new long[N - 1];
390 if (i > 0) {
391 System.arraycopy(cur, 0, ret, 0, i);
392 }
393 if (i < (N - 1)) {
394 System.arraycopy(cur, i + 1, ret, i, N - i - 1);
395 }
396 return ret;
397 }
398 }
399 return cur;
400 }
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700401
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700402 public static @Nullable long[] cloneOrNull(@Nullable long[] array) {
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700403 return (array != null) ? array.clone() : null;
404 }
Jeff Sharkeyda96e132014-07-15 14:54:09 -0700405
Jeff Sharkey2bd31db2016-01-09 16:58:14 -0700406 public static @Nullable <T> ArraySet<T> cloneOrNull(@Nullable ArraySet<T> array) {
407 return (array != null) ? new ArraySet<T>(array) : null;
408 }
409
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700410 public static @NonNull <T> ArraySet<T> add(@Nullable ArraySet<T> cur, T val) {
Jeff Sharkeyda96e132014-07-15 14:54:09 -0700411 if (cur == null) {
412 cur = new ArraySet<>();
413 }
414 cur.add(val);
415 return cur;
416 }
417
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700418 public static @Nullable <T> ArraySet<T> remove(@Nullable ArraySet<T> cur, T val) {
Jeff Sharkeyda96e132014-07-15 14:54:09 -0700419 if (cur == null) {
420 return null;
421 }
422 cur.remove(val);
423 if (cur.isEmpty()) {
424 return null;
425 } else {
426 return cur;
427 }
428 }
429
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700430 public static <T> boolean contains(@Nullable ArraySet<T> cur, T val) {
Jeff Sharkeyda96e132014-07-15 14:54:09 -0700431 return (cur != null) ? cur.contains(val) : false;
432 }
433
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700434 public static @NonNull <T> ArrayList<T> add(@Nullable ArrayList<T> cur, T val) {
Jeff Sharkeyda96e132014-07-15 14:54:09 -0700435 if (cur == null) {
436 cur = new ArrayList<>();
437 }
438 cur.add(val);
439 return cur;
440 }
441
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700442 public static @Nullable <T> ArrayList<T> remove(@Nullable ArrayList<T> cur, T val) {
Jeff Sharkeyda96e132014-07-15 14:54:09 -0700443 if (cur == null) {
444 return null;
445 }
446 cur.remove(val);
447 if (cur.isEmpty()) {
448 return null;
449 } else {
450 return cur;
451 }
452 }
453
Jeff Sharkey5217cac2015-12-20 15:34:01 -0700454 public static <T> boolean contains(@Nullable ArrayList<T> cur, T val) {
Jeff Sharkeyda96e132014-07-15 14:54:09 -0700455 return (cur != null) ? cur.contains(val) : false;
456 }
Adam Lesinski72478f02015-06-17 15:39:43 -0700457
Jeff Sharkey2bd31db2016-01-09 16:58:14 -0700458 public static @Nullable <T> T[] trimToSize(@Nullable T[] array, int size) {
459 if (array == null || size == 0) {
460 return null;
461 } else if (array.length == size) {
462 return array;
463 } else {
464 return Arrays.copyOf(array, size);
465 }
466 }
467
Adam Lesinski72478f02015-06-17 15:39:43 -0700468 /**
469 * Returns true if the two ArrayLists are equal with respect to the objects they contain.
470 * The objects must be in the same order and be reference equal (== not .equals()).
471 */
472 public static <T> boolean referenceEquals(ArrayList<T> a, ArrayList<T> b) {
473 if (a == b) {
474 return true;
475 }
476
477 final int sizeA = a.size();
478 final int sizeB = b.size();
479 if (a == null || b == null || sizeA != sizeB) {
480 return false;
481 }
482
483 boolean diff = false;
484 for (int i = 0; i < sizeA && !diff; i++) {
485 diff |= a.get(i) != b.get(i);
486 }
487 return !diff;
488 }
Adam Lesinski082614c2016-03-04 14:33:47 -0800489
490 /**
491 * Removes elements that match the predicate in an efficient way that alters the order of
492 * elements in the collection. This should only be used if order is not important.
493 * @param collection The ArrayList from which to remove elements.
494 * @param predicate The predicate that each element is tested against.
495 * @return the number of elements removed.
496 */
497 public static <T> int unstableRemoveIf(@Nullable ArrayList<T> collection,
498 @NonNull java.util.function.Predicate<T> predicate) {
499 if (collection == null) {
500 return 0;
501 }
502
503 final int size = collection.size();
504 int leftIdx = 0;
505 int rightIdx = size - 1;
506 while (leftIdx <= rightIdx) {
507 // Find the next element to remove moving left to right.
508 while (leftIdx < size && !predicate.test(collection.get(leftIdx))) {
509 leftIdx++;
510 }
511
512 // Find the next element to keep moving right to left.
513 while (rightIdx > leftIdx && predicate.test(collection.get(rightIdx))) {
514 rightIdx--;
515 }
516
517 if (leftIdx >= rightIdx) {
518 // Done.
519 break;
520 }
521
522 Collections.swap(collection, leftIdx, rightIdx);
523 leftIdx++;
524 rightIdx--;
525 }
526
527 // leftIdx is now at the end.
528 for (int i = size - 1; i >= leftIdx; i--) {
529 collection.remove(i);
530 }
531 return size - leftIdx;
532 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533}