add support to read multiple line value when loading agent jar



git-svn-id: https://svn.apache.org/repos/asf/harmony/enhanced/classlib/trunk@889056 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/modules/instrument/src/main/native/instrument/shared/inst_agt.c b/modules/instrument/src/main/native/instrument/shared/inst_agt.c
index 4155a0c..11f1b9e 100644
--- a/modules/instrument/src/main/native/instrument/shared/inst_agt.c
+++ b/modules/instrument/src/main/native/instrument/shared/inst_agt.c
@@ -206,7 +206,7 @@
 
     /* read bytes */
     size = zipEntry.uncompressedSize;
-    result = (char *)hymem_allocate_memory(size*sizeof(char));
+    result = (char *)hymem_allocate_memory(size*sizeof(char) + 1);
 #ifndef HY_ZIP_API
     retval = zip_getZipEntryData(privatePortLibrary, &zipFile, &zipEntry, (unsigned char*)result, size);
 #else /* HY_ZIP_API */
@@ -223,6 +223,7 @@
         return NULL;
     }
 
+    result[size] = '\0';
     /* free resource */
 #ifndef HY_ZIP_API
     zip_freeZipEntry(privatePortLibrary, &zipEntry);
@@ -243,6 +244,7 @@
     char *pos;
     char *end;
     char *value;
+    char *tmp;
     int length;
     PORT_ACCESS_FROM_JAVAVM(vm);
 
@@ -253,18 +255,46 @@
     pos = manifest+ (strstr(lwrmanifest,target) - lwrmanifest);
     pos += strlen(target)+2;//": "
     end = strchr(pos, '\n');
+
+    while (end != NULL && *(end + 1) == ' ') {
+        end = strchr(end + 1, '\n');
+    }
+
     if(NULL == end){
         end = manifest + strlen(manifest);
     }
-    /* in windows, has '\r\n' in the end of line, omit '\r' */
-    if (*(end - 1) == '\r'){
-        end--;
-    }
+
     length = end - pos;
 
     value = (char *)hymem_allocate_memory(sizeof(char)*(length+1));
-    strncpy(value, pos, length);
-    *(value+length) = '\0';
+    tmp = value;
+
+    end = strchr(pos, '\n');
+    while (end != NULL && *(end + 1) == ' ') {
+        /* in windows, has '\r\n' in the end of line, omit '\r' */
+        if (*(end - 1) == '\r') {
+            strncpy(tmp, pos, end - 1 - pos);
+            tmp += end - 1 - pos;
+            pos = end + 2;
+        } else {
+            strncpy(tmp, pos, end - pos);
+            tmp += end - pos;
+            pos = end + 2;
+        }
+        end = strchr(end + 1, '\n');
+    }
+
+    if (NULL == end) {
+        strcpy(tmp, pos);
+    } else {
+        /* in windows, has '\r\n' in the end of line, omit '\r' */
+        if (*(end - 1) == '\r') {
+            end--;
+        }
+        strncpy(tmp, pos, end - pos);
+        *(tmp + (end - pos)) = '\0';
+    }
+
     return value;
 }
 
@@ -316,7 +346,11 @@
     check_jvmti_error(env, (*jvmti)->GetSystemProperty(jvmti,"java.class.path",&classpath),"Failed to get classpath.");
     classpath_cpy = (char *)hymem_allocate_memory((sizeof(char)*(strlen(classpath)+strlen(jar_name)+2)));
     strcpy(classpath_cpy,classpath);
+#if defined(WIN32) || defined(WIN64)
     strcat(classpath_cpy,";");
+#else
+    strcat(classpath_cpy,":");
+#endif
     strcat(classpath_cpy,jar_name);
     check_jvmti_error(env, (*jvmti)->SetSystemProperty(jvmti, "java.class.path",classpath_cpy),"Failed to set classpath.");
     hymem_free_memory(classpath_cpy);
diff --git a/modules/instrument/src/test/java/org/apache/harmony/tests/java/lang/instrument/HelloWorldTest.java b/modules/instrument/src/test/java/org/apache/harmony/tests/java/lang/instrument/HelloWorldTest.java
new file mode 100644
index 0000000..13dde0b
--- /dev/null
+++ b/modules/instrument/src/test/java/org/apache/harmony/tests/java/lang/instrument/HelloWorldTest.java
@@ -0,0 +1,98 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.tests.java.lang.instrument;
+
+import static org.apache.harmony.tests.java.lang.instrument.InstrumentTestHelper.LINE_SEPARATOR;
+import static org.apache.harmony.tests.java.lang.instrument.InstrumentTestHelper.PREMAIN_CLASS;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.harmony.tests.java.lang.instrument.agents.HelloWorldAgent;
+
+public class HelloWorldTest extends TestCase {
+    private InstrumentTestHelper helper = null;
+
+    private static String JAR_PREFIX = "HelloWorldTest";
+
+    @Override
+    public void setUp() {
+        helper = new InstrumentTestHelper();
+    }
+
+    @Override
+    public void tearDown() {
+        helper.clean();
+    }
+
+    public void testHelloWorld() throws Exception {
+        helper.addManifestAttributes(PREMAIN_CLASS, HelloWorldAgent.class
+                .getName());
+        helper.setJarName(JAR_PREFIX + ".testHelloWorld.jar");
+        helper.setMainClass(TestMain.class);
+        List<Class> classes = new ArrayList<Class>();
+        classes.add(HelloWorldAgent.class);
+        helper.addClasses(classes);
+
+        helper.run();
+
+        assertEquals(0, helper.getExitCode());
+        assertEquals("Hello World" + LINE_SEPARATOR, helper.getStdOut());
+        assertEquals("", helper.getStdErr());
+    }
+
+    public void testMultiLineValue() throws Exception {
+        StringBuilder name = new StringBuilder(HelloWorldAgent.class.getName());
+        // line separator + one whitespace is allowed
+        name.insert(5, "\n ");
+        helper.addManifestAttributes(PREMAIN_CLASS, name.toString());
+
+        helper.setJarName(JAR_PREFIX + ".testMultiLineValue.jar");
+        helper.setMainClass(TestMain.class);
+        List<Class> classes = new ArrayList<Class>();
+        classes.add(HelloWorldAgent.class);
+        helper.addClasses(classes);
+
+        helper.run();
+
+        assertEquals(0, helper.getExitCode());
+        assertEquals("Hello World" + LINE_SEPARATOR, helper.getStdOut());
+        assertEquals("", helper.getStdErr());
+    }
+
+    public void testInvalidMultiLineValue() throws Exception {
+        StringBuilder name = new StringBuilder(HelloWorldAgent.class.getName());
+        // line separator + two whitespaces is not allowed
+        name.insert(5, "\n  ");
+        helper.addManifestAttributes("Premain-Class", name.toString());
+
+        helper.setJarName(JAR_PREFIX + ".testInvalidMultiLineValue.jar");
+        helper.setMainClass(TestMain.class);
+        List<Class> classes = new ArrayList<Class>();
+        classes.add(HelloWorldAgent.class);
+        helper.addClasses(classes);
+
+        helper.run();
+
+        assertTrue(0 != helper.getExitCode());
+        assertTrue(helper.getStdErr().contains(
+                ClassNotFoundException.class.getName()));
+    }
+}
diff --git a/modules/instrument/src/test/java/org/apache/harmony/tests/java/lang/instrument/InstrumentTestHelper.java b/modules/instrument/src/test/java/org/apache/harmony/tests/java/lang/instrument/InstrumentTestHelper.java
new file mode 100644
index 0000000..8e50c4f
--- /dev/null
+++ b/modules/instrument/src/test/java/org/apache/harmony/tests/java/lang/instrument/InstrumentTestHelper.java
@@ -0,0 +1,373 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.tests.java.lang.instrument;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+public class InstrumentTestHelper {
+    private Manifest manifest;
+
+    private List<Class> classes = new ArrayList<Class>();
+
+    private String jarName;
+
+    private Class mainClass;
+
+    private String commandAgent;
+
+    private String commandAgentOptions;
+
+    private List<String> classpath = new ArrayList<String>();
+
+    private StringBuilder stdOut = new StringBuilder();
+
+    private StringBuilder stdErr = new StringBuilder();
+
+    private int exitCode;
+
+    private String mainJarName;
+
+    private List<File> toBeCleaned = new ArrayList<File>();
+
+    public static final String PREMAIN_CLASS = "Premain-Class";
+
+    public static final String AGENT_CLASS = "Agent-Class";
+
+    public static final String BOOT_CLASS_PATH = "Boot-Class-Path";
+
+    public static final String CAN_REDEFINE_CLASSES = "Can-Redefine-Classes";
+
+    public static final String CAN_RETRANSFORM_CLASSES = "Can-Retransform-Classes";
+
+    public static final String CAN_SET_NATIVE_METHOD_PREFIX = "Can-Set-Native-Method-Prefix";
+
+    public static final String LINE_SEPARATOR = System
+            .getProperty("line.separator");
+
+    /*
+     * This variable is just for debugging. set to false, generated jar won't be
+     * deleted after testing, so you can verify if the jar was generated as you
+     * expected.
+     */
+    private boolean isDeleteJarOnExit = true;
+
+    public void clean() {
+        for (File file : toBeCleaned) {
+            file.delete();
+        }
+    }
+
+    public InstrumentTestHelper() {
+        manifest = new Manifest();
+        manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
+    }
+
+    public void addClasses(List<Class> added) {
+        classes.addAll(added);
+    }
+
+    public void addManifestAttributes(String key, String value) {
+        manifest.getMainAttributes().putValue(key, value);
+    }
+
+    public String getCommandAgent() {
+        return commandAgent;
+    }
+
+    public void setCommandAgent(String commandAgent) {
+        this.commandAgent = commandAgent;
+    }
+
+    public String getCommandAgentOptions() {
+        return commandAgentOptions;
+    }
+
+    public void setCommandAgentOptions(String commandAgentOptions) {
+        this.commandAgentOptions = commandAgentOptions;
+    }
+
+    public String getJarName() {
+        return jarName;
+    }
+
+    public void setJarName(String jarName) {
+        if (jarName.endsWith(".jar")) {
+            this.jarName = jarName;
+            mainJarName = jarName.substring(0, jarName.length() - 4)
+                    + ".main.jar";
+        } else {
+            this.jarName = jarName + ".jar";
+            mainJarName = jarName + ".main.jar";
+        }
+    }
+
+    public Class getMainClass() {
+        return mainClass;
+    }
+
+    public void setMainClass(Class mainClass) {
+        this.mainClass = mainClass;
+    }
+
+    public void run() throws Exception {
+        generateJars();
+        runAgentTest();
+    }
+
+    private void runAgentTest() throws IOException, InterruptedException {
+        String[] args = new String[2];
+        args[0] = "-javaagent:" + commandAgent;
+        if (commandAgentOptions != null
+                && commandAgentOptions.trim().length() != 0) {
+            args[0] += "=" + commandAgentOptions;
+        }
+
+        args[1] = mainClass.getName();
+
+        Process process = execJava(args, getClasspath());
+        process.waitFor();
+
+        exitCode = process.exitValue();
+    }
+
+    private Process execJava(String[] args, String[] classpath)
+            throws IOException {
+        // this function returns the resulting process from the exec
+        StringBuilder command;
+        String testVMArgs;
+        StringTokenizer st;
+
+        List<String> execArgs = new ArrayList<String>(3 + args.length);
+
+        // construct the name of executable file
+        String executable = System.getProperty("java.home");
+        if (!executable.endsWith(File.separator)) {
+            executable += File.separator;
+        }
+        executable += "bin" + File.separator + "java";
+        execArgs.add(executable);
+
+        // add classpath string
+        StringBuilder classPathString = new StringBuilder();
+        if (classpath != null && classpath.length > 0) {
+            boolean isFirst = true;
+            for (String element : classpath) {
+                if (isFirst) {
+                    isFirst = false;
+                } else {
+                    classPathString.append(File.pathSeparator);
+                }
+                classPathString.append(element);
+            }
+        }
+
+        if (classPathString.toString().length() != 0) {
+            execArgs.add("-cp");
+            execArgs.add(classPathString.toString());
+        }
+
+        // parse hy.test.vmargs if was given
+        testVMArgs = System.getProperty("hy.test.vmargs");
+        if (testVMArgs != null) {
+            st = new StringTokenizer(testVMArgs, " ");
+            while (st.hasMoreTokens()) {
+                execArgs.add(st.nextToken());
+            }
+        }
+
+        // add custom args given as parameter
+        for (String arg : args) {
+            execArgs.add(arg);
+        }
+
+        // construct command line string and print it to stdout
+        command = new StringBuilder(execArgs.get(0));
+        for (int i = 1; i < execArgs.size(); i++) {
+            command.append(" ");
+            command.append(execArgs.get(i));
+        }
+        System.out.println("Exec: " + command.toString());
+        System.out.println();
+
+        // execute java process
+        final Process proc = Runtime.getRuntime().exec(
+                execArgs.toArray(new String[execArgs.size()]));
+
+        final String lineSeparator = System.getProperty("line.separator");
+        Thread errReader = new Thread(new Runnable() {
+            public void run() {
+                BufferedReader reader = new BufferedReader(
+                        new InputStreamReader(proc.getErrorStream()));
+                String line = null;
+                try {
+                    while ((line = reader.readLine()) != null) {
+                        stdErr.append(line).append(lineSeparator);
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+        errReader.start();
+
+        Thread outReader = new Thread(new Runnable() {
+            public void run() {
+                BufferedReader reader = new BufferedReader(
+                        new InputStreamReader(proc.getInputStream()));
+                String line = null;
+                try {
+                    while ((line = reader.readLine()) != null) {
+                        stdOut.append(line).append(lineSeparator);
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+        outReader.start();
+
+        return proc;
+    }
+
+    private void generateJars() throws FileNotFoundException, IOException,
+            URISyntaxException {
+
+        File agentJar = generateJar(jarName, manifest, classes,
+                isDeleteJarOnExit);
+        toBeCleaned.add(agentJar);
+        commandAgent = agentJar.getAbsolutePath();
+
+        List<Class> list = new ArrayList<Class>();
+        list.add(mainClass);
+
+        File mainJarFile = generateJar(mainJarName, null, list,
+                isDeleteJarOnExit);
+        toBeCleaned.add(mainJarFile);
+
+        classpath.add(mainJarFile.getAbsolutePath());
+    }
+
+    private static File calculateJarDir() throws MalformedURLException,
+            URISyntaxException {
+        URL location = ClassLoader.getSystemResource(InstrumentTestHelper.class
+                .getName().replace(".", "/")
+                + ".class");
+        String locationStr = location.toString();
+
+        // calculate jarDir
+        File jarDir = null;
+        if ("jar".equals(location.getProtocol())) {
+            URL jar = null;
+            int index = locationStr.indexOf(".jar!") + 4;
+            if (locationStr.startsWith("jar:")) {
+                jar = new URL(locationStr.substring(4, index));
+            } else {
+                jar = new URL(locationStr.substring(0, index));
+            }
+            jarDir = new File(jar.toURI()).getParentFile();
+        } else {
+            int index = locationStr.lastIndexOf(InstrumentTestHelper.class
+                    .getName().replace(".", "/"));
+            URL jar = new URL(locationStr.substring(0, index));
+            jarDir = new File(jar.toURI());
+        }
+        return jarDir;
+    }
+
+    public String getStdOut() {
+        return stdOut.toString();
+    }
+
+    public String getStdErr() {
+        return stdErr.toString();
+    }
+
+    public String[] getClasspath() {
+        return classpath.toArray(new String[0]);
+    }
+
+    public void setClasspath(String[] pathes) {
+        classpath.addAll(Arrays.asList(pathes));
+    }
+
+    public int getExitCode() {
+        return exitCode;
+    }
+
+    public boolean isDeleteJarOnExit() {
+        return isDeleteJarOnExit;
+    }
+
+    public void setDeleteJarOnExit(boolean isDeleteJarOnExit) {
+        this.isDeleteJarOnExit = isDeleteJarOnExit;
+    }
+
+    public static File generateJar(String jarName, Manifest manifest,
+            List<Class> classes, boolean isDeleteOnExit)
+            throws FileNotFoundException, IOException, URISyntaxException {
+        File jarDir = calculateJarDir();
+
+        File jarFile = new File(jarDir, jarName);
+
+        JarOutputStream out = null;
+        if (manifest == null) {
+            out = new JarOutputStream(new FileOutputStream(jarFile));
+        } else {
+            out = new JarOutputStream(new FileOutputStream(jarFile), manifest);
+        }
+
+        for (Iterator<Class> iter = classes.iterator(); iter.hasNext();) {
+            Class element = iter.next();
+            String name = element.getName().replace(".", "/") + ".class";
+            InputStream in = ClassLoader.getSystemResourceAsStream(name);
+            byte[] bs = new byte[1024];
+            int count = 0;
+            ZipEntry entry = new ZipEntry(name);
+            out.putNextEntry(entry);
+            while ((count = in.read(bs)) != -1) {
+                out.write(bs, 0, count);
+            }
+            in.close();
+        }
+        out.close();
+
+        if (isDeleteOnExit) {
+            jarFile.deleteOnExit();
+        }
+
+        return jarFile;
+    }
+}
diff --git a/modules/instrument/src/test/java/org/apache/harmony/tests/java/lang/instrument/agents/HelloWorldAgent.java b/modules/instrument/src/test/java/org/apache/harmony/tests/java/lang/instrument/agents/HelloWorldAgent.java
new file mode 100644
index 0000000..24ca671
--- /dev/null
+++ b/modules/instrument/src/test/java/org/apache/harmony/tests/java/lang/instrument/agents/HelloWorldAgent.java
@@ -0,0 +1,26 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.tests.java.lang.instrument.agents;
+
+import java.lang.instrument.Instrumentation;
+
+public class HelloWorldAgent {
+    public static void premain(String agentArgs, Instrumentation inst) {
+        System.out.println("Hello World");
+    }
+}