6
\$\begingroup\$

In a custom Ant task, I have the following constants:

private static final String _REVISION = ".revision";
private static final String _CODEBASE_INFO = ".codebase";
private static final String _DIFF = ".diff";
private static final String _TYPE_GIT = "git";
private static final String _TYPE_SVN = "svn";
private static final String[] _COMMAND_REV_GIT =
    {"git", "rev-parse", "HEAD"};
private static final String[] _COMMAND_REV_SVN =
    {"svn", "info"};
private static final String[][] _COMMAND_CODEBASEINFO_GIT = {
    {"git", "branch", "-v"},
    {"git", "remote", "-v"},
    {"git", "log", "--max-count=1"},
    {"git", "status"}};
private static final String[][] _COMMAND_CODEBASEINFO_SVN = {
    _COMMAND_REV_SVN,
    {"svn", "status"}};
private static final String[][] _COMMAND_DIFF_GIT = {
    {"git", "diff", "HEAD"},
    {"git", "diff", "HEAD", "--no-prefix"}};
private static final String[][] _COMMAND_DIFF_SVN = {
    {"svn", "diff"}};

private static final Map<String, Object> _COMMANDS;

static {
    Map<String, Object> map = new HashMap<>();
    map.put(_TYPE_GIT + _REVISION, _COMMAND_REV_GIT);
    map.put(_TYPE_SVN + _REVISION, _COMMAND_REV_SVN);
    map.put(_TYPE_GIT + _DIFF, _COMMAND_DIFF_GIT);
    map.put(_TYPE_SVN + _DIFF, _COMMAND_DIFF_SVN);
    map.put(_TYPE_GIT + _CODEBASE_INFO, _COMMAND_CODEBASEINFO_GIT);
    map.put(_TYPE_SVN + _CODEBASE_INFO, _COMMAND_CODEBASEINFO_SVN);
    _COMMANDS = Collections.unmodifiableMap(map);
}

What the task should do is to run commands according to a given version control type. For example if I give it git then it will run commands in _COMMAND_REV_GIT, _COMMAND_CODEBASEINFO_GIT and _COMMAND_DIFF_GIT.

The reason I separate them into three constants is that the outputs of them have to be handled in different ways.

The reason I use a map is that I don't want to use a lot of "if" or "switch-case" in my code. In the map there are keys like git.revision, git.codebase, etc. If git is given when running this task, _COMMAND_REV_GIT will be fetched from the map using the key git.revision in the method handling running the revision command:

String[] command = _COMMANDS.get(_givenType + _REVISION);

But these constants looks really complicated, and I think the types of version control should be Enum rather than String, so is the suffixes indicating the types of the commands. So how should I modify these codes to make it clear, safe and elegant?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Would you find your code more or less readable if all of these values were inlined? That is, if there were no constants other than the map itself, and you just use the String literals everywhere. I'm not saying this is necessarily a good idea; just wondering whether you'd find the result more readable, and what reasons you had for using constants in the first place. Otherwise, using an Enum for the vc type and another Enum for the suffix sounds like a great idea. \$\endgroup\$ Commented Jan 19, 2012 at 7:56
  • \$\begingroup\$ What I want to achieve are: 1, reduce the number of if or switch-case statements, and currently I am using a static map to make it possible; 2, avoid using Strings for limited possiblities (as few as two or three). Those command arrays never changes during runtime so they are defined as constants. Directly defining them inline may not be as readable. What I concern is not a problem about "readable", but a better way, because I think the strings and the static map are ugly. \$\endgroup\$
    – Dante WWWW
    Commented Jan 19, 2012 at 9:31
  • \$\begingroup\$ Yeah, I see. I've just +1'ed palacsint's answer anyway - he/she has got it exactly right. \$\endgroup\$ Commented Jan 19, 2012 at 9:37

1 Answer 1

7
\$\begingroup\$

You should create an ScmCommands interface like this:

public interface ScmCommands {
    public String[] getRevisionCommand();

    public List<String[]> getDiffCommand();

    public List<String[]> getCodebaseInfoCommand();
}

and two implementations: SvnCommands, GitCommands.

It would improve type safety a lot. (With a Map<String, Object> you usually have to cast the values which is error-prone). Furthermore, it eliminates the magic constants. It's easy to call Map.get() with a mistyped key. With the interface the compiler warns you if the method name is wrong.


Edit: I've changed the return types. Sample implementations are the following:

public class GitCommands implements ScmCommands {

    private static final String GIT_COMMAND = "git";

    public GitCommands() {
    }

    @Override
    public String[] getRevisionCommand() {
        return new String[] { GIT_COMMAND, "rev-parse", "HEAD" };
    }

    @Override
    public List<String[]> getDiffCommand() {
        final List<String[]> result = new ArrayList<String[]>();
        result.add(new String[] { GIT_COMMAND, "diff", "HEAD" });
        result.add(new String[] { GIT_COMMAND, "diff", "HEAD", "--no-prefix" });
        return result;
    }

    @Override
    public List<String[]> getCodebaseInfoCommand() {
        final List<String[]> result = new ArrayList<String[]>();
        result.add(new String[] { GIT_COMMAND, "branch", "-v" });
        result.add(new String[] { GIT_COMMAND, "remote", "-v" });
        result.add(new String[] { GIT_COMMAND, "log", "--max-count=1" });
        result.add(new String[] { GIT_COMMAND, "status" });
        return result;
    }
}
public class SvnCommands implements ScmCommands {

    private static final String SVN_COMMAND = "svn";

    public SvnCommands() {
    }

    @Override
    public String[] getRevisionCommand() {
        return new String[] { SVN_COMMAND, "info" };
    }

    @Override
    public List<String[]> getDiffCommand() {
        final List<String[]> result = new ArrayList<String[]>();
        result.add(new String[] {SVN_COMMAND, "diff"});
        return result;
    }

    @Override
    public List<String[]> getCodebaseInfoCommand() {
        final List<String[]> result = new ArrayList<String[]>();
        result.add(new String[] { SVN_COMMAND, "status" });
        return result;
    }

}

It fits well to ProcessBuilder but I'd consider extracting out all SCM related methods to an interface (for example Scm), and move the code to SCM-specific classes which implement the Scm interface, like GitScm, SubversionScm etc. Calling command line executables doesn't smell good, maybe it would be better to use native Subversion and Git Java libraries.

\$\endgroup\$
3
  • \$\begingroup\$ +1 It looks nice! But within the implementations, what should I use to store the commands? Please note that I have one command (with its params) for revision, two commands for git diff while one for svn diff, and four commands for git's codebase info while two for svn's. And since I am using ProcessBuilder to execute the commands, they should (at last) be passed to the ProcessBuilder in an array or a list. \$\endgroup\$
    – Dante WWWW
    Commented Jan 19, 2012 at 9:42
  • \$\begingroup\$ Why do you have two or more command to the same task? (Check the update too :) \$\endgroup\$
    – palacsint
    Commented Jan 19, 2012 at 20:47
  • \$\begingroup\$ For things like codebase info, we want to collect the necessary information of the version control. We may have to run multiple commands to do that. \$\endgroup\$
    – Dante WWWW
    Commented Jan 20, 2012 at 1:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.