16bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu/* 26bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * Copyright (C) 2009 The Android Open Source Project 36bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * 46bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * Licensed under the Apache License, Version 2.0 (the "License"); 56bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * you may not use this file except in compliance with the License. 66bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * You may obtain a copy of the License at 76bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * 86bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * http://www.apache.org/licenses/LICENSE-2.0 96bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * 106bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * Unless required by applicable law or agreed to in writing, software 116bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * distributed under the License is distributed on an "AS IS" BASIS, 126bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * See the License for the specific language governing permissions and 146bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu * limitations under the License. 156bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu */ 166bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu 17f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhupackage com.android.dumprendertree; 18f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu 196bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhuimport com.android.dumprendertree.forwarder.ForwardService; 206bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu 21f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhuimport android.util.Log; 22f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu 23f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhuimport java.io.BufferedOutputStream; 24f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhuimport java.io.BufferedReader; 25f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhuimport java.io.File; 26f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhuimport java.io.FileOutputStream; 27f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhuimport java.io.FileReader; 285dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhuimport java.io.FileWriter; 29f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhuimport java.io.IOException; 30ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhuimport java.util.regex.Pattern; 31f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu 32f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhupublic class FsUtils { 33f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu 34f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu private static final String LOGTAG = "FsUtils"; 356bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu static final String HTTP_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/"; 366bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu static final String HTTPS_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/ssl/"; 376bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu static final String HTTP_LOCAL_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/local/"; 386bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu static final String HTTP_MEDIA_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/media/"; 396bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu static final String HTTP_WML_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/wml/"; 406bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu 41f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu private FsUtils() { 42f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu //no creation of instances 43f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 44f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu 45f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu public static void findLayoutTestsRecursively(BufferedOutputStream bos, 46cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block String dir, boolean ignoreResultsInDir) throws IOException { 47f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu Log.v(LOGTAG, "Searching tests under " + dir); 48f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu 49f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu File d = new File(dir); 50f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu if (!d.isDirectory()) { 51f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu throw new AssertionError("A directory expected, but got " + dir); 52f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 53cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block ignoreResultsInDir |= FileFilter.ignoreResult(dir); 54f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu 55f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu String[] files = d.list(); 56f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu for (int i = 0; i < files.length; i++) { 57f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu String s = dir + "/" + files[i]; 58cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block 59cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block File f = new File(s); 60cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block if (f.isDirectory()) { 61cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block // If this is not a test directory, we don't recurse into it. 62cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block if (!FileFilter.isNonTestDir(s)) { 63cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block Log.v(LOGTAG, "Recursing on " + s); 64cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block findLayoutTestsRecursively(bos, s, ignoreResultsInDir); 65cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block } 665e8f52f5c5ac97cbc514e72c4fc84b6fa46ebc57Steve Block continue; 675e8f52f5c5ac97cbc514e72c4fc84b6fa46ebc57Steve Block } 68cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block 69cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block // If this test should be ignored, we skip it completely. 70f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu if (FileFilter.ignoreTest(s)) { 71cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block Log.v(LOGTAG, "Ignoring: " + s); 72f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu continue; 73f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 74cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block 75cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block if ((s.toLowerCase().endsWith(".html") || s.toLowerCase().endsWith(".xml")) 76cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block && !s.endsWith("TEMPLATE.html")) { 77cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block Log.v(LOGTAG, "Recording " + s); 78f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu bos.write(s.getBytes()); 79cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block // If the result of this test should be ignored, we still run the test. 80cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block if (ignoreResultsInDir || FileFilter.ignoreResult(s)) { 81cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block bos.write((" IGNORE_RESULT").getBytes()); 82cf0fd7892b7208ebfa35809b63fc8e4d60e4d466Steve Block } 83f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu bos.write('\n'); 84f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 85f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 86f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 87f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu 88f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu public static void updateTestStatus(String statusFile, String s) { 89f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu try { 90f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu BufferedOutputStream bos = new BufferedOutputStream( 91f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu new FileOutputStream(statusFile)); 92f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu bos.write(s.getBytes()); 93f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu bos.close(); 94f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } catch (Exception e) { 95f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu Log.e(LOGTAG, "Cannot update file " + statusFile); 96f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 97f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 98f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu 99f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu public static String readTestStatus(String statusFile) { 100f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu // read out the test name it stopped last time. 101f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu String status = null; 102f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu File testStatusFile = new File(statusFile); 103f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu if(testStatusFile.exists()) { 104f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu try { 105f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu BufferedReader inReader = new BufferedReader( 106f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu new FileReader(testStatusFile)); 107f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu status = inReader.readLine(); 108f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu inReader.close(); 109f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } catch (IOException e) { 110f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu Log.e(LOGTAG, "Error reading test status.", e); 111f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 112f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 113f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu return status; 114f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu } 115f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu 1166bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu public static String getTestUrl(String path) { 1176bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu String url = null; 1186bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu if (!path.startsWith(HTTP_TESTS_PREFIX)) { 1196bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu url = "file://" + path; 1206bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu } else { 1216bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu ForwardService.getForwardService().startForwardService(); 1226bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu if (path.startsWith(HTTPS_TESTS_PREFIX)) { 1236bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu // still cut the URL after "http/tests/" 1246bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu url = "https://127.0.0.1:8443/" + path.substring(HTTP_TESTS_PREFIX.length()); 1256bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu } else if (!path.startsWith(HTTP_LOCAL_TESTS_PREFIX) 1266bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu && !path.startsWith(HTTP_MEDIA_TESTS_PREFIX) 1276bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu && !path.startsWith(HTTP_WML_TESTS_PREFIX)) { 1286bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu url = "http://127.0.0.1:8000/" + path.substring(HTTP_TESTS_PREFIX.length()); 1296bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu } else { 1306bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu url = "file://" + path; 1316bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu } 1326bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu } 1336bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu return url; 1346bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu } 1356bf18bae60ae1ff0bf2407e8db115cbbab6f1b84Guang Zhu 136ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu public static boolean diffIgnoreSpaces(String file1, String file2) throws IOException { 137ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu BufferedReader br1 = new BufferedReader(new FileReader(file1)); 138ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu BufferedReader br2 = new BufferedReader(new FileReader(file2)); 139ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu boolean same = true; 140ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu Pattern trailingSpace = Pattern.compile("\\s+$"); 141ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu 142ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu while(true) { 143ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu String line1 = br1.readLine(); 144ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu String line2 = br2.readLine(); 145ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu 146ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu if (line1 == null && line2 == null) 147ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu break; 148ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu if (line1 != null) { 149ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu line1 = trailingSpace.matcher(line1).replaceAll(""); 150ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu } else { 151ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu line1 = ""; 152ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu } 153ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu if (line2 != null) { 154ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu line2 = trailingSpace.matcher(line2).replaceAll(""); 155ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu } else { 156ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu line2 = ""; 157ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu } 158ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu if(!line1.equals(line2)) { 159ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu same = false; 160ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu break; 161ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu } 162ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu } 163ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu 164ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu br1.close(); 165ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu br2.close(); 166ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu 167ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu return same; 168ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu } 169ea48cd6dd4e64bcb5c840a12fe052f704510e01cGuang Zhu 1705dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu public static boolean isTestPageUrl(String url) { 1715dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu int qmPostion = url.indexOf('?'); 1725dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu int slashPostion = url.lastIndexOf('/'); 1735dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu if (slashPostion < qmPostion) { 1745dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu String fileName = url.substring(slashPostion + 1, qmPostion); 1755dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu if ("index.html".equals(fileName)) { 1765dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu return true; 1775dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu } 1785dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu } 1795dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu return false; 1805dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu } 1815dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu 1825dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu public static String getLastSegmentInPath(String path) { 1835dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu int endPos = path.lastIndexOf('/'); 1845dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu path = path.substring(0, endPos); 1855dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu endPos = path.lastIndexOf('/'); 1865dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu return path.substring(endPos + 1); 1875dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu } 1885dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu 1895dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu public static void writeDrawTime(String fileName, String url, long[] times) { 1905dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu StringBuffer lineBuffer = new StringBuffer(); 1915dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu // grab the last segment of path in url 1925dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu lineBuffer.append(getLastSegmentInPath(url)); 1935dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu for (long time : times) { 1945dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu lineBuffer.append('\t'); 1955dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu lineBuffer.append(time); 1965dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu } 1975dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu lineBuffer.append('\n'); 1985dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu String line = lineBuffer.toString(); 1995dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu Log.v(LOGTAG, "logging draw times: " + line); 2005dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu try { 2015dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu FileWriter fw = new FileWriter(fileName, true); 2025dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu fw.write(line); 2035dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu fw.close(); 2045dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu } catch (IOException ioe) { 2055dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu Log.e(LOGTAG, "Failed to log draw times", ioe); 2065dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu } 2075dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu } 2085dc4f21ab6360b45f464c1451f8d403dd4df3c63Guang Zhu 209f92bd42a702af7047ac4bd7c95b4a82973b7a79dGuang Zhu} 210