/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.netbeans.lib.cvsclient.commandLine;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.ResourceBundle;
import org.netbeans.lib.cvsclient.CVSRoot;
import org.netbeans.lib.cvsclient.Client;
import org.netbeans.lib.cvsclient.admin.StandardAdminHandler;
import org.netbeans.lib.cvsclient.command.BasicCommand;
import org.netbeans.lib.cvsclient.command.Command;
import org.netbeans.lib.cvsclient.command.CommandAbortedException;
import org.netbeans.lib.cvsclient.command.CommandException;
import org.netbeans.lib.cvsclient.command.GlobalOptions;
import org.netbeans.lib.cvsclient.commandLine.command.CommandProvider;
import org.netbeans.lib.cvsclient.connection.AuthenticationException;
import org.netbeans.lib.cvsclient.connection.Connection;
import org.netbeans.lib.cvsclient.connection.ConnectionFactory;
import org.netbeans.lib.cvsclient.connection.PServerConnection;
import org.netbeans.lib.cvsclient.connection.PasswordsFile;
import org.netbeans.lib.cvsclient.connection.StandardScrambler;
import org.netbeans.lib.cvsclient.event.CVSListener;
/**
* An implementation of the standard CVS client utility (command line tool) in Java
*
* @author Robert Greig
*/
public class CVSCommand {
private static final String HELP_OPTIONS = "--help-options"; // NOI18N
private static final String HELP_COMMANDS = "--help-commands"; // NOI18N
private static final String HELP_SYNONYMS = "--help-synonyms"; // NOI18N
/**
* The path to the repository on the server
*/
private String repository;
/**
* The local path to use to perform operations (the top level)
*/
private String localPath;
/**
* The connection to the server
*/
private Connection connection;
/**
* The client that manages interactions with the server
*/
private Client client;
/**
* The global options being used. GlobalOptions are only global for a particular command.
*/
private GlobalOptions globalOptions;
/**
* The port number that is used to connect to the remote server. It is taken into account only when it's value is greater then zero.
*/
private int port = 0;
/**
* Execute a configured CVS command
*
* @param command
* the command to execute
* @throws CommandException
* if there is an error running the command
*/
public boolean executeCommand(Command command, PrintStream stderr) throws CommandException, AuthenticationException {
client.setErrorStream(stderr);
return client.executeCommand(command, globalOptions);
}
public void setRepository(String repository) {
this.repository = repository;
}
public void setLocalPath(String localPath) {
this.localPath = localPath;
}
public void setGlobalOptions(GlobalOptions globalOptions) {
this.globalOptions = globalOptions;
}
/**
* Creates the connection and the client and connects.
*/
private void connect(CVSRoot root, String password) throws IllegalArgumentException, AuthenticationException, CommandAbortedException {
connection = ConnectionFactory.getConnection(root);
if (CVSRoot.METHOD_PSERVER.equals(root.getMethod())) {
((PServerConnection) connection).setEncodedPassword(password);
if (port > 0) {
((PServerConnection) connection).setPort(port);
}
}
connection.open();
client = new Client(connection, new StandardAdminHandler());
client.setLocalPath(localPath);
}
private void addListener(CVSListener listener) {
if (client != null) {
// add a listener to the client
client.getEventManager().addCVSListener(listener);
}
}
private void close(PrintStream stderr) {
try {
connection.close();
} catch (IOException e) {
stderr.println("Unable to close connection: " + e);
// e.printStackTrace();
}
}
/**
* Obtain the CVS root, either from the -D option cvs.root or from the CVS directory
*
* @return the CVSRoot string
*/
private static String getCVSRoot(String workingDir) {
String root = null;
BufferedReader r = null;
if (workingDir == null) {
workingDir = System.getProperty("user.dir");
}
try {
File f = new File(workingDir);
File rootFile = new File(f, "CVS/Root");
if (rootFile.exists()) {
r = new BufferedReader(new FileReader(rootFile));
root = r.readLine();
}
} catch (IOException e) {
// ignore
} finally {
try {
if (r != null) {
r.close();
}
} catch (IOException e) {
System.err.println("Warning: could not close CVS/Root file!");
}
}
if (root == null) {
root = System.getProperty("cvs.root");
}
return root;
}
/**
* Process global options passed into the application
*
* @param args
* the argument list, complete
* @param globalOptions
* the global options structure that will be passed to the command
*/
private static int processGlobalOptions(String[] args, GlobalOptions globalOptions, PrintStream stderr) {
final String getOptString = globalOptions.getOptString();
GetOpt go = new GetOpt(args, getOptString);
int ch = -1;
boolean usagePrint = false;
while ((ch = go.getopt()) != GetOpt.optEOF) {
// System.out.println("Global option '"+((char) ch)+"', '"+go.optArgGet()+"'");
boolean success = globalOptions.setCVSCommand((char) ch, go.optArgGet());
if (!success) {
usagePrint = true;
}
}
if (usagePrint) {
showUsage(stderr);
return -10;
}
return go.optIndexGet();
}
private static void showUsage(PrintStream stderr) {
String usageStr = ResourceBundle.getBundle(CVSCommand.class.getPackage().getName() + ".Bundle").getString("MSG_HelpUsage"); // NOI18N
stderr.println(MessageFormat.format(usageStr, new Object[] { HELP_OPTIONS, HELP_COMMANDS, HELP_SYNONYMS }));
// stderr.println("Usage: cvs [global options] command [command-options-and-arguments]");
// stderr.println(" specify "+HELP_OPTIONS+" for a list of options");
// stderr.println(" specify "+HELP_COMMANDS+" for a list of commands");
// stderr.println(" specify "+HELP_SYNONYMS+" for a list of command synonyms");
}
/**
* Perform the 'login' command, asking the user for a password. If the login is successful, the password is written to a file. The
* file's location is user.home, unless the cvs.passfile option is set.
*
* @param userName
* the userName
* @param hostName
* the host
*/
private static boolean performLogin(String userName, String hostName, String repository, int port, GlobalOptions globalOptions) {
PServerConnection c = new PServerConnection();
c.setUserName(userName);
String password = null;
try {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Enter password: ");
password = in.readLine();
} catch (IOException e) {
System.err.println("Could not read password: " + e);
return false;
}
String encodedPassword = StandardScrambler.getInstance().scramble(password);
c.setEncodedPassword(encodedPassword);
c.setHostName(hostName);
c.setRepository(repository);
c.setPort(port);
try {
c.verify();
} catch (AuthenticationException e) {
System.err.println("Could not login to host " + hostName);
return false;
}
// was successful, so write the appropriate file out
// we look for cvs.passfile being set, but if not use user.dir
// as the default
String root = globalOptions.getCVSRoot();
try {
PasswordsFile.storePassword(root, encodedPassword);
System.err.println("Logged in successfully to " + repository + " on host " + hostName);
return true;
} catch (IOException e) {
System.err.println("Error: could not write password file.");
return false;
}
}
/**
* Lookup the password in the .cvspass file. This file is looked for in the user.home directory if the option cvs.passfile is not set
*
* @param CVSRoot
* the CVS root for which the password is being searched
* @return the password, scrambled
*/
private static String lookupPassword(String CVSRoot, String CVSRootWithPort, PrintStream stderr) {
File passFile = new File(System.getProperty("cvs.passfile", System.getProperty("user.home") + "/.cvspass"));
BufferedReader reader = null;
String password = null;
try {
reader = new BufferedReader(new FileReader(passFile));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("/1 ")) {
line = line.substring("/1 ".length());
}
if (line.startsWith(CVSRoot + " ")) {
password = line.substring(CVSRoot.length() + 1);
break;
} else if (line.startsWith(CVSRootWithPort + " ")) {
password = line.substring(CVSRootWithPort.length() + 1);
break;
}
}
} catch (IOException e) {
stderr.println("Could not read password for host: " + e);
return null;
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
stderr.println("Warning: could not close password file.");
}
}
}
if (password == null) {
stderr.println("Didn't find password for CVSROOT '" + CVSRoot + "'.");
}
return password;
}
/**
* Execute the CVS command and exit JVM.
*/
public static void main(String[] args) {
if (processCommand(args, null, System.getProperty("user.dir"), System.out, System.err)) {
System.exit(0);
} else {
System.exit(1);
}
}
/**
* Process the CVS command passed in args[] array with all necessary options. The only difference from main() method is, that this
* method does not exit the JVM and provides command output.
*
* @param args
* The command with options
* @param files
* The files to execute the command on.
* @param localPath
* The local working directory
* @param stdout
* The standard output of the command
* @param stderr
* The error output of the command.
*/
public static boolean processCommand(String[] args, File[] files, String localPath, PrintStream stdout, PrintStream stderr) {
return processCommand(args, files, localPath, 0, stdout, stderr);
}
/**
* Process the CVS command passed in args[] array with all necessary options. The only difference from main() method is, that this
* method does not exit the JVM and provides command output.
*
* @param args
* The command with options
* @param files
* The files to execute the command on.
* @param localPath
* The local working directory
* @param port
* The port number that is used to connect to the remote server. It is taken into account only when it's value is greater
* then zero.
* @param stdout
* The standard output of the command
* @param stderr
* The error output of the command.
* @return whether the command was processed successfully
*/
public static boolean processCommand(String[] args, File[] files, String localPath, int port, PrintStream stdout, PrintStream stderr) {
assert stdout != null : "The output stream must be defined."; // NOI18N
assert stderr != null : "The error stream must be defined."; // NOI18N
// Provide help if requested
if (args.length > 0) {
if (HELP_OPTIONS.equals(args[0])) {
printHelpOptions(stdout);
return true;
} else if (HELP_COMMANDS.equals(args[0])) {
printHelpCommands(stdout);
return true;
} else if (HELP_SYNONYMS.equals(args[0])) {
printHelpSynonyms(stdout);
return true;
}
}
try { // Adjust the local path
localPath = new File(localPath).getCanonicalPath();
} catch (IOException ioex) {
}
// Set up the CVSRoot. Note that it might still be null after this
// call if the user has decided to set it with the -d command line
// global option
GlobalOptions globalOptions = new GlobalOptions();
globalOptions.setCVSRoot(getCVSRoot(localPath));
// Set up any global options specified. These occur before the
// name of the command to run
int commandIndex = -1;
try {
commandIndex = processGlobalOptions(args, globalOptions, stderr);
if (commandIndex == -10) {
return true;
}
} catch (IllegalArgumentException e) {
stderr.println("Invalid argument: " + e);
return false;
}
if (globalOptions.isShowHelp()) {
printHelp(commandIndex, args, stdout, stderr);
return true;
}
if (globalOptions.isShowVersion()) {
printVersion(stdout, stderr);
return true;
}
// if we don't have a CVS root by now, the user has messed up
if (globalOptions.getCVSRoot() == null) {
stderr.println("No CVS root is set. Use the cvs.root " + "property, e.g. java -Dcvs.root=\":pserver:user@host:/usr/cvs\""
+ " or start the application in a directory containing a CVS subdirectory" + " or use the -d command switch.");
return false;
}
// parse the CVS root into its constituent parts
CVSRoot root = null;
final String cvsRoot = globalOptions.getCVSRoot();
try {
root = CVSRoot.parse(cvsRoot);
} catch (IllegalArgumentException e) {
stderr.println("Incorrect format for CVSRoot: " + cvsRoot + "\nThe correct format is: "
+ "[:method:][[user][:password]@][hostname:[port]]/path/to/repository" + "\nwhere \"method\" is pserver.");
return false;
}
// if we had some options without any command, then the user messed up
if (commandIndex >= args.length) {
showUsage(stderr);
return false;
}
final String command = args[commandIndex];
if (command.equals("login")) {
if (CVSRoot.METHOD_PSERVER.equals(root.getMethod())) {
return performLogin(root.getUserName(), root.getHostName(), root.getRepository(), root.getPort(), globalOptions);
} else {
stderr.println("login does not apply for connection type " + "\'" + root.getMethod() + "\'");
return false;
}
}
// this is not login, but a 'real' cvs command, so construct it,
// set the options, and then connect to the server and execute it
Command c = null;
try {
c = CommandFactory.getDefault().createCommand(command, args, ++commandIndex, globalOptions, localPath);
} catch (IllegalArgumentException e) {
stderr.println("Illegal argument: " + e.getMessage());
return false;
}
if (files != null && c instanceof BasicCommand) {
((BasicCommand) c).setFiles(files);
}
String password = null;
if (CVSRoot.METHOD_PSERVER.equals(root.getMethod())) {
password = root.getPassword();
if (password != null) {
password = StandardScrambler.getInstance().scramble(password);
} else {
if (port > 0) {
root.setPort(port);
}
password = lookupPassword(cvsRoot, root.toString(), stderr);
if (password == null) {
password = StandardScrambler.getInstance().scramble(""); // an empty password
}
}
}
CVSCommand cvsCommand = new CVSCommand();
cvsCommand.setGlobalOptions(globalOptions);
cvsCommand.setRepository(root.getRepository());
if (port > 0) {
cvsCommand.port = port;
}
// the local path is just the path where we executed the
// command. This is the case for command-line CVS but not
// usually for GUI front-ends
cvsCommand.setLocalPath(localPath);
try {
cvsCommand.connect(root, password);
CVSListener list;
if (c instanceof ListenerProvider) {
list = ((ListenerProvider) c).createCVSListener(stdout, stderr);
} else {
list = new BasicListener(stdout, stderr);
}
cvsCommand.addListener(list);
boolean status = cvsCommand.executeCommand(c, stderr);
return status;
} catch (AuthenticationException aex) {
stderr.println(aex.getLocalizedMessage());
return false;
} catch (CommandAbortedException caex) {
stderr.println("Error: " + caex);
Thread.currentThread().interrupt();
return false;
} catch (Exception t) {
stderr.println("Error: " + t);
t.printStackTrace(stderr);
return false;
} finally {
if (cvsCommand != null) {
cvsCommand.close(stderr);
}
}
}
private static void printHelpOptions(PrintStream stdout) {
String options = ResourceBundle.getBundle(CVSCommand.class.getPackage().getName() + ".Bundle").getString("MSG_HelpOptions"); // NOI18N
stdout.println(options);
}
private static void printHelpCommands(PrintStream stdout) {
String msg = ResourceBundle.getBundle(CVSCommand.class.getPackage().getName() + ".Bundle").getString("MSG_CVSCommands"); // NOI18N
stdout.println(msg);
CommandProvider[] providers = CommandFactory.getDefault().getCommandProviders();
Arrays.sort(providers, new CommandProvidersComparator());
int maxNameLength = 0;
for (int i = 0; i < providers.length; i++) {
int l = providers[i].getName().length();
if (maxNameLength < l) {
maxNameLength = l;
}
}
maxNameLength += 2; // Two spaces from the longest name
for (int i = 0; i < providers.length; i++) {
stdout.print("\t" + providers[i].getName());
char spaces[] = new char[maxNameLength - providers[i].getName().length()];
Arrays.fill(spaces, ' ');
stdout.print(new String(spaces));
providers[i].printShortDescription(stdout);
stdout.println();
}
}
private static void printHelpSynonyms(PrintStream stdout) {
String msg = ResourceBundle.getBundle(CVSCommand.class.getPackage().getName() + ".Bundle").getString("MSG_CVSSynonyms"); // NOI18N
stdout.println(msg);
CommandProvider[] providers = CommandFactory.getDefault().getCommandProviders();
Arrays.sort(providers, new CommandProvidersComparator());
int maxNameLength = 0;
for (int i = 0; i < providers.length; i++) {
int l = providers[i].getName().length();
if (maxNameLength < l) {
maxNameLength = l;
}
}
maxNameLength += 2; // Two spaces from the longest name
for (int i = 0; i < providers.length; i++) {
String[] synonyms = providers[i].getSynonyms();
if (synonyms.length > 0) {
stdout.print("\t" + providers[i].getName());
char spaces[] = new char[maxNameLength - providers[i].getName().length()];
Arrays.fill(spaces, ' ');
stdout.print(new String(spaces));
for (int j = 0; j < synonyms.length; j++) {
stdout.print(synonyms[j] + " ");
}
stdout.println();
}
}
}
private static void printHelp(int commandIndex, String[] args, PrintStream stdout, PrintStream stderr) {
if (commandIndex >= args.length) {
showUsage(stdout);
} else {
String cmdName = args[commandIndex];
CommandProvider provider = CommandFactory.getDefault().getCommandProvider(cmdName);
if (provider == null) {
printUnknownCommand(cmdName, stderr);
} else {
provider.printLongDescription(stdout);
}
}
}
private static void printVersion(PrintStream stdout, PrintStream stderr) {
String version = CVSCommand.class.getPackage().getSpecificationVersion();
stdout.println("Java Concurrent Versions System (JavaCVS) " + version + " (client)");
}
private static void printUnknownCommand(String commandName, PrintStream out) {
String msg = ResourceBundle.getBundle(CVSCommand.class.getPackage().getName() + ".Bundle").getString("MSG_UnknownCommand"); // NOI18N
out.println(MessageFormat.format(msg, new Object[] { commandName }));
printHelpCommands(out);
}
private static final class CommandProvidersComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) {
if (!(o1 instanceof CommandProvider) || !(o2 instanceof CommandProvider)) {
throw new IllegalArgumentException("Can not compare objects " + o1 + " and " + o2);
}
return ((CommandProvider) o1).getName().compareTo(((CommandProvider) o2).getName());
}
}
}