1f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin/* 2f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Copyright (C) 2010 The Android Open Source Project 3f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 4f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Licensed under the Apache License, Version 2.0 (the "License"); 5f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * you may not use this file except in compliance with the License. 6f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * You may obtain a copy of the License at 7f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 8f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * http://www.apache.org/licenses/LICENSE-2.0 9f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 10f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Unless required by applicable law or agreed to in writing, software 11f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * distributed under the License is distributed on an "AS IS" BASIS, 12f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * See the License for the specific language governing permissions and 14f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * limitations under the License. 15f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */ 16f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 17f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpackage com.android.gallery3d.data; 18f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 19f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.common.Utils; 20f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.util.IdentityCache; 21f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 22f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.lang.ref.WeakReference; 23f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.ArrayList; 24f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 25f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class Path { 26f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String TAG = "Path"; 27f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static Path sRoot = new Path(null, "ROOT"); 28f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 29f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final Path mParent; 30f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private final String mSegment; 31f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private WeakReference<MediaObject> mObject; 32f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private IdentityCache<String, Path> mChildren; 33f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 34f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private Path(Path parent, String segment) { 35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mParent = parent; 36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mSegment = segment; 37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 38f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 39f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public Path getChild(String segment) { 40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (Path.class) { 41f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mChildren == null) { 42f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mChildren = new IdentityCache<String, Path>(); 43f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 44f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Path p = mChildren.get(segment); 45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (p != null) return p; 46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 47f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 48f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Path p = new Path(this, segment); 49f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mChildren.put(segment, p); 50f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return p; 51f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 52f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 53f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 54f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public Path getParent() { 55f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (Path.class) { 56f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mParent; 57f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 58f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 59f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public Path getChild(int segment) { 61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return getChild(String.valueOf(segment)); 62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 64f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public Path getChild(long segment) { 65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return getChild(String.valueOf(segment)); 66f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void setObject(MediaObject object) { 69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (Path.class) { 70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Utils.assertTrue(mObject == null || mObject.get() == null); 71f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mObject = new WeakReference<MediaObject>(object); 72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 75676d4762496eddae66930c6f8b0bae22a22b3ef6Owen Lin MediaObject getObject() { 76f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (Path.class) { 77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return (mObject == null) ? null : mObject.get(); 78f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 80f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 81f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 820addfc7f6342184a67cdd8b5cc3872c6a5c87e55Doris Liu // TODO: toString() should be more efficient, will fix it later 83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public String toString() { 84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (Path.class) { 85f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin StringBuilder sb = new StringBuilder(); 86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String[] segments = split(); 87f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int i = 0; i < segments.length; i++) { 88f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin sb.append("/"); 89f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin sb.append(segments[i]); 90f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 91f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return sb.toString(); 92f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 93f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 94f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 950addfc7f6342184a67cdd8b5cc3872c6a5c87e55Doris Liu public boolean equalsIgnoreCase (String p) { 960addfc7f6342184a67cdd8b5cc3872c6a5c87e55Doris Liu String path = toString(); 970addfc7f6342184a67cdd8b5cc3872c6a5c87e55Doris Liu return path.equalsIgnoreCase(p); 980addfc7f6342184a67cdd8b5cc3872c6a5c87e55Doris Liu } 990addfc7f6342184a67cdd8b5cc3872c6a5c87e55Doris Liu 100f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public static Path fromString(String s) { 101f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (Path.class) { 102f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String[] segments = split(s); 103f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Path current = sRoot; 104f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int i = 0; i < segments.length; i++) { 105f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin current = current.getChild(segments[i]); 106f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return current; 108f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 109f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public String[] split() { 112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (Path.class) { 113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int n = 0; 114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (Path p = this; p != sRoot; p = p.mParent) { 115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin n++; 116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String[] segments = new String[n]; 118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int i = n - 1; 119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (Path p = this; p != sRoot; p = p.mParent) { 120f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin segments[i--] = p.mSegment; 121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return segments; 123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 124f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 125f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 126f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public static String[] split(String s) { 127f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int n = s.length(); 128f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (n == 0) return new String[0]; 129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (s.charAt(0) != '/') { 130f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin throw new RuntimeException("malformed path:" + s); 131f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 132f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ArrayList<String> segments = new ArrayList<String>(); 133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int i = 1; 134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin while (i < n) { 135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int brace = 0; 136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int j; 137f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (j = i; j < n; j++) { 138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin char c = s.charAt(j); 139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (c == '{') ++brace; 140f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin else if (c == '}') --brace; 141f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin else if (brace == 0 && c == '/') break; 142f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (brace != 0) { 144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin throw new RuntimeException("unbalanced brace in path:" + s); 145f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 146f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin segments.add(s.substring(i, j)); 147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin i = j + 1; 148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String[] result = new String[segments.size()]; 150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin segments.toArray(result); 151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return result; 152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Splits a string to an array of strings. 155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // For example, "{foo,bar,baz}" -> {"foo","bar","baz"}. 156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public static String[] splitSequence(String s) { 157f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int n = s.length(); 158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (s.charAt(0) != '{' || s.charAt(n-1) != '}') { 159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin throw new RuntimeException("bad sequence: " + s); 160f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 161f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ArrayList<String> segments = new ArrayList<String>(); 162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int i = 1; 163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin while (i < n - 1) { 164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int brace = 0; 165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int j; 166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (j = i; j < n - 1; j++) { 167f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin char c = s.charAt(j); 168f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (c == '{') ++brace; 169f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin else if (c == '}') --brace; 170f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin else if (brace == 0 && c == ',') break; 171f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 172f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (brace != 0) { 173f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin throw new RuntimeException("unbalanced brace in path:" + s); 174f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 175f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin segments.add(s.substring(i, j)); 176f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin i = j + 1; 177f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 178f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String[] result = new String[segments.size()]; 179f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin segments.toArray(result); 180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return result; 181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 183f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public String getPrefix() { 18431cac30e46a293f6d0901e22c6ab0d083cae7021Owen Lin if (this == sRoot) return ""; 18531cac30e46a293f6d0901e22c6ab0d083cae7021Owen Lin return getPrefixPath().mSegment; 18631cac30e46a293f6d0901e22c6ab0d083cae7021Owen Lin } 18731cac30e46a293f6d0901e22c6ab0d083cae7021Owen Lin 18831cac30e46a293f6d0901e22c6ab0d083cae7021Owen Lin public Path getPrefixPath() { 189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (Path.class) { 190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Path current = this; 19131cac30e46a293f6d0901e22c6ab0d083cae7021Owen Lin if (current == sRoot) { 19231cac30e46a293f6d0901e22c6ab0d083cae7021Owen Lin throw new IllegalStateException(); 19331cac30e46a293f6d0901e22c6ab0d083cae7021Owen Lin } 194f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin while (current.mParent != sRoot) { 195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin current = current.mParent; 196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 19731cac30e46a293f6d0901e22c6ab0d083cae7021Owen Lin return current; 198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 199f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public String getSuffix() { 202f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // We don't need lock because mSegment is final. 203f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mSegment; 204f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 205f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 206f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Below are for testing/debugging only 207f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin static void clearAll() { 208f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (Path.class) { 209f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin sRoot = new Path(null, ""); 210f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 211f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 212f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 213f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin static void dumpAll() { 214f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin dumpAll(sRoot, "", ""); 215f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 216f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 217f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin static void dumpAll(Path p, String prefix1, String prefix2) { 218f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin synchronized (Path.class) { 219f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin MediaObject obj = p.getObject(); 220f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Log.d(TAG, prefix1 + p.mSegment + ":" 221f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin + (obj == null ? "null" : obj.getClass().getSimpleName())); 222f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (p.mChildren != null) { 223f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ArrayList<String> childrenKeys = p.mChildren.keys(); 224f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int i = 0, n = childrenKeys.size(); 225f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (String key : childrenKeys) { 226f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Path child = p.mChildren.get(key); 227f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (child == null) { 228f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ++i; 229f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin continue; 230f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 231f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Log.d(TAG, prefix2 + "|"); 232f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (++i < n) { 233f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin dumpAll(child, prefix2 + "+-- ", prefix2 + "| "); 234f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 235f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin dumpAll(child, prefix2 + "+-- ", prefix2 + " "); 236f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 237f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 238f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 239f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 240f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 241f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin} 242