15d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao/*
25d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * Copyright (C) 2008 The Android Open Source Project
35d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao *
45d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * Licensed under the Apache License, Version 2.0 (the "License");
55d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * you may not use this file except in compliance with the License.
65d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * You may obtain a copy of the License at
75d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao *
85d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao *      http://www.apache.org/licenses/LICENSE-2.0
95d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao *
105d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * Unless required by applicable law or agreed to in writing, software
115d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * distributed under the License is distributed on an "AS IS" BASIS,
125d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * See the License for the specific language governing permissions and
145d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * limitations under the License.
155d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao */
165d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
175d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhaoimport java.io.File;
185d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhaoimport java.io.FileNotFoundException;
195d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhaoimport java.io.IOException;
205d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhaoimport java.io.RandomAccessFile;
215d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhaoimport java.lang.reflect.Constructor;
225d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhaoimport java.lang.reflect.Method;
235d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhaoimport java.lang.reflect.InvocationTargetException;
245d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
255d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao/**
265d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * A class loader with atypical behavior: we try to load a private
275d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * class implementation before asking the system or boot loader.  This
285d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * is used to create multiple classes with identical names in a single VM.
295d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao *
305d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * If DexFile is available, we use that; if not, we assume we're not in
315d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * Dalvik and instantiate the class with defineClass().
325d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao *
335d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * The location of the DEX files and class data is dependent upon the
345d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao * test framework.
355d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao */
365d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhaopublic class FancyLoader extends ClassLoader {
375d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    /* this is where the "alternate" .class files live */
384d9716c19cc25911e639272048abd0d6702bb082Brian Carlstrom    static final String CLASS_PATH = "classes-ex/";
395d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
405d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    /* this is the "alternate" DEX/Jar file */
411ed1a1320c89a143a8139d331a05d47ed3db64f7Mathieu Chartier    static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/068-classloader-ex.jar";
425d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
435d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    /* on Dalvik, this is a DexFile; otherwise, it's null */
445d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    private Class mDexClass;
455d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
465d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    private Object mDexFile;
475d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
485d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    /**
495d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * Construct FancyLoader, grabbing a reference to the DexFile class
505d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * if we're running under Dalvik.
515d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     */
525d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    public FancyLoader(ClassLoader parent) {
535d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        super(parent);
545d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
555d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        try {
56741b5b7ef4c7fd4a786364bbf60d515489caff47Elliott Hughes            mDexClass = parent.loadClass("dalvik.system.DexFile");
575d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        } catch (ClassNotFoundException cnfe) {
585d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            // ignore -- not running Dalvik
595d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
605d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    }
615d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
625d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    /**
635d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * Finds the class with the specified binary name.
645d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     *
655d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * We search for a file in CLASS_PATH or pull an entry from DEX_FILE.
665d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * If we don't find a match, we throw an exception.
675d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     */
685d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    protected Class<?> findClass(String name) throws ClassNotFoundException
695d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    {
705d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        if (mDexClass != null) {
715d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            return findClassDalvik(name);
725d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        } else {
735d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            return findClassNonDalvik(name);
745d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
755d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    }
765d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
775d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    /**
785d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * Finds the class with the specified binary name, from a DEX file.
795d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     */
805d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    private Class<?> findClassDalvik(String name)
815d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        throws ClassNotFoundException {
825d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
835d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        if (mDexFile == null) {
845d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            synchronized (FancyLoader.class) {
855d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                Constructor ctor;
865d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                /*
875d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                 * Construct a DexFile object through reflection.
885d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                 */
895d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                try {
905d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                    ctor = mDexClass.getConstructor(new Class[] {String.class});
915d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                } catch (NoSuchMethodException nsme) {
925d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                    throw new ClassNotFoundException("getConstructor failed",
935d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                        nsme);
945d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                }
955d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
965d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                try {
974d9716c19cc25911e639272048abd0d6702bb082Brian Carlstrom                    mDexFile = ctor.newInstance(DEX_FILE);
985d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                } catch (InstantiationException ie) {
995d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                    throw new ClassNotFoundException("newInstance failed", ie);
1005d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                } catch (IllegalAccessException iae) {
1015d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                    throw new ClassNotFoundException("newInstance failed", iae);
1025d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                } catch (InvocationTargetException ite) {
1035d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                    throw new ClassNotFoundException("newInstance failed", ite);
1045d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                }
1055d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            }
1065d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
1075d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1085d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        /*
1095d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * Call DexFile.loadClass(String, ClassLoader).
1105d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         */
1115d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        Method meth;
1125d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1135d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        try {
1145d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            meth = mDexClass.getMethod("loadClass",
1155d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                    new Class[] { String.class, ClassLoader.class });
1165d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        } catch (NoSuchMethodException nsme) {
1175d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            throw new ClassNotFoundException("getMethod failed", nsme);
1185d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
1195d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1205d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        try {
1215d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            meth.invoke(mDexFile, name, this);
1225d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        } catch (IllegalAccessException iae) {
1235d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            throw new ClassNotFoundException("loadClass failed", iae);
1245d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        } catch (InvocationTargetException ite) {
1255d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            throw new ClassNotFoundException("loadClass failed",
1265d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                ite.getCause());
1275d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
1285d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1295d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        return null;
1305d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    }
1315d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1325d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    /**
1335d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * Finds the class with the specified binary name, from .class files.
1345d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     */
1355d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    private Class<?> findClassNonDalvik(String name)
1365d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        throws ClassNotFoundException {
1375d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1385d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        String pathName = CLASS_PATH + name + ".class";
1395d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        //System.out.println("--- Fancy: looking for " + pathName);
1405d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1415d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        File path = new File(pathName);
1425d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        RandomAccessFile raf;
1435d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1445d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        try {
1455d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            raf = new RandomAccessFile(path, "r");
1465d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        } catch (FileNotFoundException fnfe) {
1475d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            throw new ClassNotFoundException("Not found: " + pathName);
1485d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
1495d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1505d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        /* read the entire file in */
1515d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        byte[] fileData;
1525d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        try {
1535d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            fileData = new byte[(int) raf.length()];
1545d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            raf.readFully(fileData);
1555d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        } catch (IOException ioe) {
1565d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            throw new ClassNotFoundException("Read error: " + pathName);
1575d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        } finally {
1585d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            try {
1595d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                raf.close();
1605d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            } catch (IOException ioe) {
1615d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                // drop
1625d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            }
1635d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
1645d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1655d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        /* create the class */
1665d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        //System.out.println("--- Fancy: defining " + name);
1675d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        try {
1685d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            return defineClass(name, fileData, 0, fileData.length);
1695d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        } catch (Throwable th) {
1705d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            throw new ClassNotFoundException("defineClass failed", th);
1715d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
1725d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    }
1735d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1745d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    /**
1755d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * Load a class.
1765d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     *
1775d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * Normally a class loader wouldn't override this, but we want our
1785d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * version of the class to take precedence over an already-loaded
1795d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * version.
1805d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     *
1815d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * We still want the system classes (e.g. java.lang.Object) from the
1825d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     * bootstrap class loader.
1835d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao     */
1845d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    protected Class<?> loadClass(String name, boolean resolve)
1855d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        throws ClassNotFoundException
1865d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    {
1875d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        Class res;
1885d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
1895d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        /*
1905d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * 1. Invoke findLoadedClass(String) to check if the class has
1915d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * already been loaded.
1925d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         *
1935d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * This doesn't change.
1945d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         */
1955d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        res = findLoadedClass(name);
1965d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        if (res != null) {
1975d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            System.out.println("FancyLoader.loadClass: "
1985d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                + name + " already loaded");
1995d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            if (resolve)
2005d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                resolveClass(res);
2015d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            return res;
2025d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
2035d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
2045d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        /*
2055d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * 3. Invoke the findClass(String) method to find the class.
2065d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         */
2075d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        try {
2085d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            res = findClass(name);
2095d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            if (resolve)
2105d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao                resolveClass(res);
2115d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
2125d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        catch (ClassNotFoundException e) {
2135d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao            // we couldn't find it, so eat the exception and keep going
2145d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        }
2155d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao
2165d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        /*
2175d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * 2. Invoke the loadClass method on the parent class loader.  If
2185d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * the parent loader is null the class loader built-in to the
2195d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * virtual machine is used, instead.
2205d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         *
2215d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * (Since we're not in java.lang, we can't actually invoke the
2225d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * parent's loadClass() method, but we passed our parent to the
2235d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         * super-class which can take care of it for us.)
2245d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao         */
2255d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        res = super.loadClass(name, resolve);   // returns class or throws
2265d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao        return res;
2275d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao    }
2285d1ac920fdaef5d4ec8f66bb734488cd9660b024jeffhao}
229