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.ui;
18f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
19f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.nio.Buffer;
20f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.nio.ByteBuffer;
21f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.nio.ByteOrder;
22f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.nio.CharBuffer;
23f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.nio.DoubleBuffer;
24f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.nio.FloatBuffer;
25f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.nio.IntBuffer;
26f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.nio.LongBuffer;
27f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.nio.ShortBuffer;
28f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
29f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport javax.microedition.khronos.opengles.GL10;
30f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
31f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class PointerInfo {
32f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
33f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    /**
34f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * The number of coordinates per vertex. 1..4
35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     */
36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int mSize;
37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
38f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    /**
39f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * The type of each coordinate.
40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     */
41f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int mType;
42f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
43f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    /**
44f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * The byte offset between consecutive vertices. 0 means mSize *
45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * sizeof(mType)
46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     */
47f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int mStride;
48f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public Buffer mPointer;
49f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public ByteBuffer mTempByteBuffer;
50f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
51f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public PointerInfo(int size, int type, int stride, Buffer pointer) {
52f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mSize = size;
53f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mType = type;
54f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mStride = stride;
55f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mPointer = pointer;
56f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
57f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
58f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private int getStride() {
59f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mStride > 0 ? mStride : sizeof(mType) * mSize;
60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void bindByteBuffer() {
63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mTempByteBuffer = mPointer == null ? null : toByteBuffer(-1, mPointer);
64f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
66f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void unbindByteBuffer() {
67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mTempByteBuffer = null;
68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static int sizeof(int type) {
71f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        switch (type) {
72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        case GL10.GL_UNSIGNED_BYTE:
73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return 1;
74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        case GL10.GL_BYTE:
75f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return 1;
76f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        case GL10.GL_SHORT:
77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return 2;
78f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        case GL10.GL_FIXED:
79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return 4;
80f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        case GL10.GL_FLOAT:
81f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return 4;
82f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        default:
83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return 0;
84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
85f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
87f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static ByteBuffer toByteBuffer(int byteCount, Buffer input) {
88f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        ByteBuffer result = null;
89f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        boolean convertWholeBuffer = (byteCount < 0);
90f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (input instanceof ByteBuffer) {
91f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            ByteBuffer input2 = (ByteBuffer) input;
92f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int position = input2.position();
93f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (convertWholeBuffer) {
94f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                byteCount = input2.limit() - position;
95f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
96f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            result = ByteBuffer.allocate(byteCount).order(input2.order());
97f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            for (int i = 0; i < byteCount; i++) {
98f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result.put(input2.get());
99f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
100f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            input2.position(position);
101f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (input instanceof CharBuffer) {
102f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            CharBuffer input2 = (CharBuffer) input;
103f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int position = input2.position();
104f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (convertWholeBuffer) {
105f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                byteCount = (input2.limit() - position) * 2;
106f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            result = ByteBuffer.allocate(byteCount).order(input2.order());
108f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            CharBuffer result2 = result.asCharBuffer();
109f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            for (int i = 0; i < byteCount / 2; i++) {
110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result2.put(input2.get());
111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            input2.position(position);
113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (input instanceof ShortBuffer) {
114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            ShortBuffer input2 = (ShortBuffer) input;
115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int position = input2.position();
116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (convertWholeBuffer) {
117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                byteCount = (input2.limit() - position)* 2;
118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            result = ByteBuffer.allocate(byteCount).order(input2.order());
120f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            ShortBuffer result2 = result.asShortBuffer();
121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            for (int i = 0; i < byteCount / 2; i++) {
122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result2.put(input2.get());
123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
124f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            input2.position(position);
125f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (input instanceof IntBuffer) {
126f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            IntBuffer input2 = (IntBuffer) input;
127f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int position = input2.position();
128f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (convertWholeBuffer) {
129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                byteCount = (input2.limit() - position) * 4;
130f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
131f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            result = ByteBuffer.allocate(byteCount).order(input2.order());
132f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            IntBuffer result2 = result.asIntBuffer();
133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            for (int i = 0; i < byteCount / 4; i++) {
134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result2.put(input2.get());
135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            input2.position(position);
137f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (input instanceof FloatBuffer) {
138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            FloatBuffer input2 = (FloatBuffer) input;
139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int position = input2.position();
140f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (convertWholeBuffer) {
141f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                byteCount = (input2.limit() - position) * 4;
142f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            result = ByteBuffer.allocate(byteCount).order(input2.order());
144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            FloatBuffer result2 = result.asFloatBuffer();
145f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            for (int i = 0; i < byteCount / 4; i++) {
146f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result2.put(input2.get());
147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            input2.position(position);
149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (input instanceof DoubleBuffer) {
150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            DoubleBuffer input2 = (DoubleBuffer) input;
151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int position = input2.position();
152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (convertWholeBuffer) {
153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                byteCount = (input2.limit() - position) * 8;
154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            result = ByteBuffer.allocate(byteCount).order(input2.order());
156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            DoubleBuffer result2 = result.asDoubleBuffer();
157f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            for (int i = 0; i < byteCount / 8; i++) {
158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result2.put(input2.get());
159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
160f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            input2.position(position);
161f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (input instanceof LongBuffer) {
162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            LongBuffer input2 = (LongBuffer) input;
163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int position = input2.position();
164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (convertWholeBuffer) {
165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                byteCount = (input2.limit() - position) * 8;
166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
167f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            result = ByteBuffer.allocate(byteCount).order(input2.order());
168f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            LongBuffer result2 = result.asLongBuffer();
169f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            for (int i = 0; i < byteCount / 8; i++) {
170f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result2.put(input2.get());
171f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
172f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            input2.position(position);
173f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
174f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            throw new RuntimeException("Unimplemented Buffer subclass.");
175f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
176f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        result.rewind();
177f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // The OpenGL API will interpret the result in hardware byte order,
178f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // so we better do that as well:
179f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        result.order(ByteOrder.nativeOrder());
180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return result;
181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
183f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void getArrayElement(int index, double[] result) {
184f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mTempByteBuffer == null) {
185f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            throw new IllegalArgumentException("undefined pointer");
186f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
187f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mStride < 0) {
188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            throw new IllegalArgumentException("invalid stride");
189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int stride = getStride();
192f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        ByteBuffer byteBuffer = mTempByteBuffer;
193f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int size = mSize;
194f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int type = mType;
195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int sizeofType = sizeof(type);
196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int byteOffset = stride * index;
197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        for (int i = 0; i < size; i++) {
199f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            switch (type) {
200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            case GL10.GL_BYTE:
201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            case GL10.GL_UNSIGNED_BYTE:
202f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result[i] = byteBuffer.get(byteOffset);
203f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                break;
204f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            case GL10.GL_SHORT:
205f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
206f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result[i] = shortBuffer.get(byteOffset / 2);
207f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                break;
208f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            case GL10.GL_FIXED:
209f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                IntBuffer intBuffer = byteBuffer.asIntBuffer();
210f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result[i] = intBuffer.get(byteOffset / 4);
211f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                break;
212f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            case GL10.GL_FLOAT:
213f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();
214f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                result[i] = floatBuffer.get(byteOffset / 4);
215f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                break;
216f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            default:
217f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                throw new UnsupportedOperationException("unknown type");
218f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
219f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            byteOffset += sizeofType;
220f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
221f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
222f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin}
223