Bug 51985

Summary: System.out.print is adding an extra line feed when flushed as a result of a System.in.readLine() call
Product: Ant Reporter: Scott Asire <scott.asire423>
Component: CoreAssignee: Ant Notifications List <notifications>
Status: NEW ---    
Severity: normal    
Priority: P2    
Version: 1.8.2   
Target Milestone: ---   
Hardware: PC   
OS: All   
Attachments: Test task definition that reproduces the problem
Build file that calls the ConfirmInput task.

Description Scott Asire 2011-10-06 23:46:53 UTC
Here's a custom task definition that asks the user to input their name and then dupes it back:

package com.runamok.tools.taskdefs;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 *
 */
public class ConfirmInputTask  extends Task
{
  private BufferedReader _in = new BufferedReader( new InputStreamReader( System.in ) );

  public void execute() throws BuildException
  {
    System.out.print("Enter your name:");
    try
    {
      String name = _in.readLine();
      System.out.println("You entered: " + name);
    }
    catch (IOException e)
    {
      log(e, Project.MSG_ERR);
    }
  }
}

The expected behavior is as follows:

C:\niku\main.next\tools>ant -f test-confirm.xml build
Buildfile: C:\niku\main.next\tools\test-confirm.xml

build:
Enter your name:test
You entered: test

BUILD SUCCESSFUL
Total time: 2 seconds

C:\niku\main.next\tools>

The actual behavior is:

C:\niku\main.next\tools>ant -f test-confirm.xml build
Buildfile: C:\niku\main.next\tools\test-confirm.xml

build:
Enter your name:
test
You entered: test

BUILD SUCCESSFUL
Total time: 2 seconds

C:\niku\main.next\tools>

The DemuxOutputStream class that ANT uses to take over System.out appears to do a println() when it is called to flush the buffer as a result of the DemuxInputStream readLine() call. 

If I remove the two lines in org.apache.tools.ant.Main.runBuild() that override System.out and System.err I get the expected behavior (see commented out lines below)

   private void runBuild(ClassLoader coreLoader) throws BuildException {

        if (!readyToRun) {
            return;
        }

        final Project project = new Project();
        project.setCoreLoader(coreLoader);

        Throwable error = null;

        try {
            addBuildListeners(project);
            addInputHandler(project);

            PrintStream savedErr = System.err;
            PrintStream savedOut = System.out;
            InputStream savedIn = System.in;

            // use a system manager that prevents from System.exit()
            SecurityManager oldsm = null;
            oldsm = System.getSecurityManager();

                //SecurityManager can not be installed here for backwards
                //compatibility reasons (PD). Needs to be loaded prior to
                //ant class if we are going to implement it.
                //System.setSecurityManager(new NoExitSecurityManager());
            try {
                if (allowInput) {
                    project.setDefaultInputStream(System.in);
                }
              System.setIn(new DemuxInputStream(project));
// SA               System.setOut(new PrintStream(new DemuxOutputStream(project, false)));
// SA               System.setErr(new PrintStream(new DemuxOutputStream(project, true)));


                if (!projectHelp) {
                    project.fireBuildStarted();
                }

                // set the thread priorities
                if (threadPriority != null) {
                    try {
                        project.log("Setting Ant's thread priority to "
                                + threadPriority, Project.MSG_VERBOSE);
                        Thread.currentThread().setPriority(threadPriority.intValue());
                    } catch (SecurityException swallowed) {
                        //we cannot set the priority here.
                        project.log("A security manager refused to set the -nice value");
                    }
                }



                project.init();

                // resolve properties
                PropertyHelper propertyHelper
                    = (PropertyHelper) PropertyHelper.getPropertyHelper(project);
                HashMap props = new HashMap(definedProps);
                new ResolvePropertyMap(project, propertyHelper,
                                       propertyHelper.getExpanders())
                    .resolveAllProperties(props, null, false);

                // set user-define properties
                for (Iterator e = props.entrySet().iterator(); e.hasNext(); ) {
                    Map.Entry ent = (Map.Entry) e.next();
                    String arg = (String) ent.getKey();
                    Object value = ent.getValue();
                    project.setInheritedProperty(arg, String.valueOf(value));
                }

                project.setInheritedProperty(MagicNames.ANT_FILE,
                                        buildFile.getAbsolutePath());
                project.setInheritedProperty(MagicNames.ANT_FILE_TYPE,
                                        MagicNames.ANT_FILE_TYPE_FILE);

                project.setKeepGoingMode(keepGoingMode);
                if (proxy) {
                    //proxy setup if enabled
                    ProxySetup proxySetup = new ProxySetup(project);
                    proxySetup.enableProxies();
                }

                ProjectHelper.configureProject(project, buildFile);

                if (projectHelp) {
                    printDescription(project);
                    printTargets(project, msgOutputLevel > Project.MSG_INFO,
                            msgOutputLevel > Project.MSG_VERBOSE);
                    return;
                }

                // make sure that we have a target to execute
                if (targets.size() == 0) {
                    if (project.getDefaultTarget() != null) {
                        targets.addElement(project.getDefaultTarget());
                    }
                }

                project.executeTargets(targets);
            } finally {
                // put back the original security manager
                //The following will never eval to true. (PD)
                if (oldsm != null) {
                    System.setSecurityManager(oldsm);
                }

                System.setOut(savedOut);
                System.setErr(savedErr);
                System.setIn(savedIn);
            }
        } catch (RuntimeException exc) {
            error = exc;
            throw exc;
        } catch (Error e) {
            error = e;
            throw e;
        } finally {
            if (!projectHelp) {
                try {
                    project.fireBuildFinished(error);
                } catch (Throwable t) {
                    // yes, I know it is bad style to catch Throwable,
                    // but if we don't, we lose valuable information
                    System.err.println("Caught an exception while logging the"
                                       + " end of the build.  Exception was:");
                    t.printStackTrace();
                    if (error != null) {
                        System.err.println("There has been an error prior to"
                                           + " that:");
                        error.printStackTrace();
                    }
                    throw new BuildException(t);
                }
            } else if (error != null) {
                project.log(error.toString(), Project.MSG_ERR);
            }
        }
    }
Comment 1 Scott Asire 2011-10-06 23:49:20 UTC
Created attachment 27714 [details]
Test task definition that reproduces the problem

This Task demonstrates the incorrect behavior on System.out.print followed by a System.in.readLine(). The input should be accepted on the same line as the message.
Comment 2 Scott Asire 2011-10-06 23:51:03 UTC
Created attachment 27715 [details]
Build file that calls the ConfirmInput task.

Build file that I used to test the ConfirmInputTask. I run it using the command:

ant -f test-confirm.xml build