aboutsummaryrefslogtreecommitdiffstats
path: root/sdkmanager
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-01-09 17:51:19 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-01-09 17:51:19 -0800
commita1514dae8668c48feae5436285e3db0ba2133ec3 (patch)
tree115c685f5c4c810cd317e8a6903e8d2d96749051 /sdkmanager
parent9ca1c0b33cc3fc28c21a915730797ec01e71a152 (diff)
downloadsdk-a1514dae8668c48feae5436285e3db0ba2133ec3.zip
sdk-a1514dae8668c48feae5436285e3db0ba2133ec3.tar.gz
sdk-a1514dae8668c48feae5436285e3db0ba2133ec3.tar.bz2
auto import from //branches/cupcake/...@125939
Diffstat (limited to 'sdkmanager')
-rw-r--r--sdkmanager/app/.classpath2
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java580
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/Main.java222
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java152
-rw-r--r--sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java180
-rw-r--r--sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java105
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java30
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java40
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java3
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java14
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java37
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java19
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java346
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java95
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java22
-rw-r--r--sdkmanager/libs/sdkuilib/.classpath8
-rw-r--r--sdkmanager/libs/sdkuilib/.project17
-rw-r--r--sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java56
-rw-r--r--sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/VmSelector.java379
19 files changed, 2145 insertions, 162 deletions
diff --git a/sdkmanager/app/.classpath b/sdkmanager/app/.classpath
index 45c59d3..cbd9d37 100644
--- a/sdkmanager/app/.classpath
+++ b/sdkmanager/app/.classpath
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="tests"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
<classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
<classpathentry combineaccessrules="false" kind="src" path="/SdkUiLib"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
new file mode 100644
index 0000000..ef3d0ee
--- /dev/null
+++ b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed 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 com.android.sdkmanager;
+
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+/**
+ * Parses the command-line and stores flags needed or requested.
+ * <p/>
+ * This is a base class. To be useful you want to:
+ * <ul>
+ * <li>override it.
+ * <li>pass an action array to the constructor.
+ * <li>define flags for your actions.
+ * </ul>
+ * <p/>
+ * To use, call {@link #parseArgs(String[])} and then call {@link #getValue(String, String)}.
+ */
+public class CommandLineProcessor {
+
+ /** Internal action name for all global flags. */
+ public final static String GLOBAL_FLAG = "global";
+ /** Internal action name for internally hidden flags.
+ * This is currently used to store the requested action name. */
+ public final static String INTERNAL_FLAG = "internal";
+
+ /** The global help flag. */
+ public static final String KEY_HELP = "help";
+ /** The global verbose flag. */
+ public static final String KEY_VERBOSE = "verbose";
+ /** The internal action flag. */
+ public static final String KEY_ACTION = "action";
+
+ /** List of available actions.
+ * <p/>
+ * Each entry must be a 2-string array with first the action name and then
+ * a description.
+ */
+ private final String[][] mActions;
+ /** The hash of all defined arguments.
+ * <p/>
+ * The key is a string "action/longName".
+ */
+ private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>();
+
+ public CommandLineProcessor(String[][] actions) {
+ mActions = actions;
+
+ define(MODE.STRING, false, INTERNAL_FLAG, null, KEY_ACTION, "Selected Action", null);
+
+ define(MODE.BOOLEAN, false, GLOBAL_FLAG, "v", KEY_VERBOSE, "Verbose mode", false);
+ define(MODE.BOOLEAN, false, GLOBAL_FLAG, "h", KEY_HELP, "This help", false);
+ }
+
+ //------------------
+ // Helpers to get flags values
+
+ /** Helper that returns true if --verbose was requested. */
+ public boolean isVerbose() {
+ return ((Boolean) getValue(GLOBAL_FLAG, KEY_VERBOSE)).booleanValue();
+ }
+
+ /** Helper that returns true if --help was requested. */
+ public boolean isHelpRequested() {
+ return ((Boolean) getValue(GLOBAL_FLAG, KEY_HELP)).booleanValue();
+ }
+
+ /** Helper that returns the requested action name. */
+ public String getActionRequested() {
+ return (String) getValue(INTERNAL_FLAG, KEY_ACTION);
+ }
+
+ //------------------
+
+ /**
+ * Raw access to parsed parameter values.
+ * @param action The action name, including {@link #GLOBAL_FLAG} and {@link #INTERNAL_FLAG}
+ * @param longFlagName The long flag name for the given action.
+ * @return The current value object stored in the parameter, which depends on the argument mode.
+ */
+ public Object getValue(String action, String longFlagName) {
+ String key = action + "/" + longFlagName;
+ Arg arg = mArguments.get(key);
+ return arg.getCurrentValue();
+ }
+
+ /**
+ * Internal setter for raw parameter value.
+ * @param action The action name, including {@link #GLOBAL_FLAG} and {@link #INTERNAL_FLAG}
+ * @param longFlagName The long flag name for the given action.
+ * @param value The new current value object stored in the parameter, which depends on the
+ * argument mode.
+ */
+ protected void setValue(String action, String longFlagName, Object value) {
+ String key = action + "/" + longFlagName;
+ Arg arg = mArguments.get(key);
+ arg.setCurrentValue(value);
+ }
+
+ /**
+ * Parses the command-line arguments.
+ * <p/>
+ * This method will exit and not return if a parsing error arise.
+ *
+ * @param args The arguments typically received by a main method.
+ */
+ public void parseArgs(String[] args) {
+ String needsHelp = null;
+ String action = null;
+
+ int n = args.length;
+ for (int i = 0; i < n; i++) {
+ Arg arg = null;
+ String a = args[i];
+ if (a.startsWith("--")) {
+ arg = findLongArg(action, a.substring(2));
+ } else if (a.startsWith("-")) {
+ arg = findShortArg(action, a.substring(1));
+ }
+
+ // Not a keyword and we don't have an action yet, this should be an action
+ if (arg == null && action == null) {
+
+ if (a.startsWith("-")) {
+ // Got a keyword but not valid for global flags
+ needsHelp = String.format(
+ "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the action name?",
+ a, action);
+ break;
+ }
+
+ for (String[] actionDesc : mActions) {
+ if (actionDesc[0].equals(a)) {
+ action = a;
+ break;
+ }
+ }
+
+ if (action == null) {
+ needsHelp = String.format(
+ "Expected action name after global parameters but found %1$s instead.",
+ a);
+ break;
+ }
+ } else if (arg == null && action != null) {
+ // Got a keyword but not valid for the current action
+ needsHelp = String.format(
+ "Flag '%1$s' is not valid for action '%2$s'.",
+ a, action);
+ break;
+
+ } else if (arg != null) {
+ // Process keyword
+ String error = null;
+ if (arg.getMode().needsExtra()) {
+ if (++i >= n) {
+ needsHelp = String.format("Missing argument for flag %1$s.", a);
+ break;
+ }
+
+ error = arg.getMode().process(arg, args[i]);
+ } else {
+ error = arg.getMode().process(arg, null);
+
+ // If we just toggled help, we want to exit now without printing any error.
+ // We do this test here only when a Boolean flag is toggled since booleans
+ // are the only flags that don't take parameters and help is a boolean.
+ if (isHelpRequested()) {
+ printHelpAndExit(null);
+ // The call above should terminate however in unit tests we override
+ // it so we still need to return here.
+ return;
+ }
+ }
+
+ if (error != null) {
+ needsHelp = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
+ break;
+ }
+ }
+ }
+
+ if (needsHelp == null) {
+ if (action == null) {
+ needsHelp = "Missing action name.";
+ } else {
+ // Validate that all mandatory arguments are non-null for this action
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getAction().equals(action)) {
+ if (arg.isMandatory() && arg.getCurrentValue() == null) {
+ needsHelp = String.format("The parameter --%1$s must be defined for action '%2$s'",
+ arg.getLongArg(),
+ action);
+ break;
+ }
+ }
+ }
+
+ setValue(INTERNAL_FLAG, KEY_ACTION, action);
+ }
+ }
+
+ if (needsHelp != null) {
+ printHelpAndExitForAction(action, needsHelp);
+ }
+ }
+
+ /**
+ * Finds an {@link Arg} given an action name and a long flag name.
+ * @return The {@link Arg} found or null.
+ */
+ protected Arg findLongArg(String action, String longName) {
+ if (action == null) {
+ action = GLOBAL_FLAG;
+ }
+ String key = action + "/" + longName;
+ return mArguments.get(key);
+ }
+
+ /**
+ * Finds an {@link Arg} given an action name and a short flag name.
+ * @return The {@link Arg} found or null.
+ */
+ protected Arg findShortArg(String action, String shortName) {
+ if (action == null) {
+ action = GLOBAL_FLAG;
+ }
+
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getAction().equals(action)) {
+ if (shortName.equals(arg.getShortArg())) {
+ return arg;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Prints the help/usage and exits.
+ *
+ * @param errorFormat Optional error message to print prior to usage using String.format
+ * @param args Arguments for String.format
+ */
+ public void printHelpAndExit(String errorFormat, Object... args) {
+ printHelpAndExitForAction(null /*actionFilter*/, errorFormat, args);
+ }
+
+ /**
+ * Prints the help/usage and exits.
+ *
+ * @param actionFilter If null, displays help for all actions. If not null, display help only
+ * for that specific action. In all cases also display general usage and action list.
+ * @param errorFormat Optional error message to print prior to usage using String.format
+ * @param args Arguments for String.format
+ */
+ public void printHelpAndExitForAction(String actionFilter, String errorFormat, Object... args) {
+ if (errorFormat != null) {
+ stderr(errorFormat, args);
+ }
+
+ /*
+ * usage should fit in 80 columns
+ * 12345678901234567890123456789012345678901234567890123456789012345678901234567890
+ */
+ stdout("\n" +
+ "Usage:\n" +
+ " android [global options] action [action options]\n" +
+ "\n" +
+ "Global options:");
+ listOptions(GLOBAL_FLAG);
+
+ stdout("\nValid actions:");
+ for (String[] action : mActions) {
+ String filler = "";
+ int len = action[0].length();
+ if (len < 10) {
+ filler = " ".substring(len);
+ }
+
+ stdout("- %1$s:%2$s %3$s", action[0], filler, action[1]);
+ }
+
+ for (String[] action : mActions) {
+ if (actionFilter == null || actionFilter.equals(action[0])) {
+ stdout("\nAction \"%1$s\":", action[0]);
+ stdout(" %1$s", action[1]);
+ stdout("Options:");
+ listOptions(action[0]);
+ }
+ }
+
+ exit();
+ }
+
+ /**
+ * Internal helper to print all the option flags for a given action name.
+ */
+ protected void listOptions(String action) {
+ int numOptions = 0;
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getAction().equals(action)) {
+
+ String value = null;
+ if (arg.getDefaultValue() instanceof String[]) {
+ value = "";
+ for (String v : (String[]) arg.getDefaultValue()) {
+ if (value.length() > 0) {
+ value += "|";
+ }
+ value += v;
+ }
+ } else if (arg.getDefaultValue() != null) {
+ value = arg.getDefaultValue().toString();
+ }
+
+ stdout(" -%1$s %2$-10s %3$s%4$s",
+ arg.getShortArg(),
+ "--" + arg.getLongArg(),
+ arg.getDescription(),
+ value == null ? "" : " (" + value + ")");
+ numOptions++;
+ }
+ }
+
+ if (numOptions == 0) {
+ stdout(" No options");
+ }
+ }
+
+ //----
+
+ /**
+ * The mode of an argument specifies the type of variable it represents,
+ * whether an extra parameter is required after the flag and how to parse it.
+ */
+ static enum MODE {
+ /** Argument value is a Boolean. Default value is a Boolean. */
+ BOOLEAN {
+ @Override
+ public boolean needsExtra() {
+ return false;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ // Toggle the current value
+ arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue());
+ return null;
+ }
+ },
+
+ /** Argument value is an Integer. Default value is an Integer. */
+ INTEGER {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ try {
+ arg.setCurrentValue(Integer.parseInt(extra));
+ return null;
+ } catch (NumberFormatException e) {
+ return String.format("Failed to parse '%1$s' as an integer: %2%s",
+ extra, e.getMessage());
+ }
+ }
+ },
+
+ /** Argument value is a String. Default value is a String[]. */
+ ENUM {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ StringBuilder desc = new StringBuilder();
+ String[] values = (String[]) arg.getDefaultValue();
+ for (String value : values) {
+ if (value.equals(extra)) {
+ arg.setCurrentValue(extra);
+ return null;
+ }
+
+ if (desc.length() != 0) {
+ desc.append(", ");
+ }
+ desc.append(value);
+ }
+
+ return String.format("'%1$s' is not one of %2$s", extra, desc.toString());
+ }
+ },
+
+ /** Argument value is a String. Default value is a null. */
+ STRING {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ arg.setCurrentValue(extra);
+ return null;
+ }
+ };
+
+ /**
+ * Returns true if this mode requires an extra parameter.
+ */
+ public abstract boolean needsExtra();
+
+ /**
+ * Processes the flag for this argument.
+ *
+ * @param arg The argument being processed.
+ * @param extra The extra parameter. Null if {@link #needsExtra()} returned false.
+ * @return An error string or null if there's no error.
+ */
+ public abstract String process(Arg arg, String extra);
+ }
+
+ /**
+ * An argument accepted by the command-line, also called "a flag".
+ * Arguments must have a short version (one letter), a long version name and a description.
+ * They can have a default value, or it can be null.
+ * Depending on the {@link MODE}, the default value can be a Boolean, an Integer, a String
+ * or a String array (in which case the first item is the current by default.)
+ */
+ static class Arg {
+ private final String mAction;
+ private final String mShortName;
+ private final String mLongName;
+ private final String mDescription;
+ private final Object mDefaultValue;
+ private Object mCurrentValue;
+ private final MODE mMode;
+ private final boolean mMandatory;
+
+ /**
+ * Creates a new argument flag description.
+ *
+ * @param mode The {@link MODE} for the argument.
+ * @param mandatory True if this argument is mandatory for this action.
+ * @param action The action name. Can be #GLOBAL_FLAG or #INTERNAL_FLAG.
+ * @param shortName The one-letter short argument name. Cannot be empty nor null.
+ * @param longName The long argument name. Cannot be empty nor null.
+ * @param description The description. Cannot be null.
+ * @param defaultValue The default value (or values), which depends on the selected {@link MODE}.
+ */
+ public Arg(MODE mode,
+ boolean mandatory,
+ String action,
+ String shortName,
+ String longName,
+ String description,
+ Object defaultValue) {
+ mMode = mode;
+ mMandatory = mandatory;
+ mAction = action;
+ mShortName = shortName;
+ mLongName = longName;
+ mDescription = description;
+ mDefaultValue = defaultValue;
+ if (defaultValue instanceof String[]) {
+ mCurrentValue = ((String[])defaultValue)[0];
+ } else {
+ mCurrentValue = mDefaultValue;
+ }
+ }
+
+ public boolean isMandatory() {
+ return mMandatory;
+ }
+
+ public String getShortArg() {
+ return mShortName;
+ }
+
+ public String getLongArg() {
+ return mLongName;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public String getAction() {
+ return mAction;
+ }
+
+ public Object getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ public Object getCurrentValue() {
+ return mCurrentValue;
+ }
+
+ public void setCurrentValue(Object currentValue) {
+ mCurrentValue = currentValue;
+ }
+
+ public MODE getMode() {
+ return mMode;
+ }
+ }
+
+ /**
+ * Internal helper to define a new argument for a give action.
+ *
+ * @param mode The {@link MODE} for the argument.
+ * @param action The action name. Can be #GLOBAL_FLAG or #INTERNAL_FLAG.
+ * @param shortName The one-letter short argument name. Cannot be empty nor null.
+ * @param longName The long argument name. Cannot be empty nor null.
+ * @param description The description. Cannot be null.
+ * @param defaultValue The default value (or values), which depends on the selected {@link MODE}.
+ */
+ protected void define(MODE mode,
+ boolean mandatory,
+ String action,
+ String shortName, String longName,
+ String description, Object defaultValue) {
+ assert(mandatory || mode == MODE.BOOLEAN); // a boolean mode cannot be mandatory
+
+ String key = action + "/" + longName;
+ mArguments.put(key, new Arg(mode, mandatory,
+ action, shortName, longName, description, defaultValue));
+ }
+
+ /**
+ * Exits in case of error.
+ * This is protected so that it can be overridden in unit tests.
+ */
+ protected void exit() {
+ System.exit(1);
+ }
+
+ /**
+ * Prints a line to stdout.
+ * This is protected so that it can be overridden in unit tests.
+ *
+ * @param format The string to be formatted. Cannot be null.
+ * @param args Format arguments.
+ */
+ protected void stdout(String format, Object...args) {
+ System.out.println(String.format(format, args));
+ }
+
+ /**
+ * Prints a line to stderr.
+ * This is protected so that it can be overridden in unit tests.
+ *
+ * @param format The string to be formatted. Cannot be null.
+ * @param args Format arguments.
+ */
+ protected void stderr(String format, Object...args) {
+ System.err.println(String.format(format, args));
+ }
+}
diff --git a/sdkmanager/app/src/com/android/sdkmanager/Main.java b/sdkmanager/app/src/com/android/sdkmanager/Main.java
index 72bd2aa..3bcb9a3 100644
--- a/sdkmanager/app/src/com/android/sdkmanager/Main.java
+++ b/sdkmanager/app/src/com/android/sdkmanager/Main.java
@@ -23,6 +23,8 @@ import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import com.android.sdklib.project.ProjectCreator;
+import com.android.sdklib.project.ProjectCreator.OutputLevel;
import com.android.sdklib.vm.HardwareProperties;
import com.android.sdklib.vm.VmManager;
import com.android.sdklib.vm.HardwareProperties.HardwareProperty;
@@ -35,31 +37,20 @@ import java.util.List;
import java.util.Map;
/**
- * Main class for the 'android' application
- *
+ * Main class for the 'android' application.
*/
class Main {
private final static String TOOLSDIR = "com.android.sdkmanager.toolsdir";
- private final static String ARG_LIST_TARGET = "target";
- private final static String ARG_LIST_VM = "vm";
-
private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" };
private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" };
private String mSdkFolder;
+ private ISdkLog mSdkLog;
private SdkManager mSdkManager;
private VmManager mVmManager;
-
- /* --list parameters */
- private String mListObject;
-
- /* --create parameters */
- private boolean mCreateVm;
- private int mCreateTargetId;
- private IAndroidTarget mCreateTarget;
- private String mCreateName;
+ private SdkCommandLine mSdkCommandLine;
public static void main(String[] args) {
new Main().run(args);
@@ -71,7 +62,7 @@ class Main {
*/
private void run(String[] args) {
init();
- parseArgs(args);
+ mSdkCommandLine.parseArgs(args);
parseSdk();
doAction();
}
@@ -81,70 +72,41 @@ class Main {
* doing basic parsing of the SDK.
*/
private void init() {
+ mSdkCommandLine = new SdkCommandLine();
+
/* We get passed a property for the tools dir */
String toolsDirProp = System.getProperty(TOOLSDIR);
if (toolsDirProp == null) {
// for debugging, it's easier to override using the process environment
toolsDirProp = System.getenv(TOOLSDIR);
}
- if (toolsDirProp == null) {
- printHelpAndExit("ERROR: The tools directory property is not set, please make sure you are executing android or android.bat");
- }
-
- // got back a level for the SDK folder
- File tools = new File(toolsDirProp);
- mSdkFolder = tools.getParent();
-
- }
- /**
- * Parses command-line arguments, or prints help/usage and exits if error.
- * @param args arguments passed to the program
- */
- private void parseArgs(String[] args) {
- final int numArgs = args.length;
-
- try {
- int argPos = 0;
- for (; argPos < numArgs; argPos++) {
- final String arg = args[argPos];
- if (arg.equals("-l") || arg.equals("--list")) {
- mListObject = args[++argPos];
- } else if (arg.equals("-c") || arg.equals("--create")) {
- mCreateVm = true;
- parseCreateArgs(args, ++argPos);
+ if (toolsDirProp != null) {
+ // got back a level for the SDK folder
+ File tools;
+ if (toolsDirProp.length() > 0) {
+ tools = new File(toolsDirProp);
+ mSdkFolder = tools.getParent();
+ } else {
+ try {
+ tools = new File(".").getCanonicalFile();
+ mSdkFolder = tools.getParent();
+ } catch (IOException e) {
+ // Will print an error below since mSdkFolder is not defined
}
}
- } catch (ArrayIndexOutOfBoundsException e) {
- /* Any OOB triggers help */
- printHelpAndExit("ERROR: Not enough arguments.");
}
- }
- private void parseCreateArgs(String[] args, int argPos) {
- final int numArgs = args.length;
-
- try {
- for (; argPos < numArgs; argPos++) {
- final String arg = args[argPos];
- if (arg.equals("-t") || arg.equals("--target")) {
- String targetId = args[++argPos];
- try {
- // get the target id
- mCreateTargetId = Integer.parseInt(targetId);
- } catch (NumberFormatException e) {
- printHelpAndExit("ERROR: Target Id is not a number");
- }
- } else if (arg.equals("-n") || arg.equals("--name")) {
- mCreateName = args[++argPos];
- } else {
- printHelpAndExit("ERROR: '%s' unknown argument for --create mode",
- args[argPos]);
- }
+ if (mSdkFolder == null) {
+ String os = System.getProperty("os.name");
+ String cmd = "android";
+ if (os.startsWith("Windows")) {
+ cmd += ".bat";
}
- } catch (ArrayIndexOutOfBoundsException e) {
- /* Any OOB triggers help */
- printHelpAndExit("ERROR: Not enough arguments for --create");
+
+ mSdkCommandLine.printHelpAndExit(
+ "ERROR: The tools directory property is not set, please make sure you are executing %1$s",
+ cmd);
}
}
@@ -152,10 +114,15 @@ class Main {
* Does the basic SDK parsing required for all actions
*/
private void parseSdk() {
- mSdkManager = SdkManager.createManager(mSdkFolder, new ISdkLog() {
- public void error(String errorFormat, Object... args) {
- System.err.printf("Error: " + errorFormat, args);
- System.err.println("");
+ mSdkLog = new ISdkLog() {
+ public void error(Throwable t, String errorFormat, Object... args) {
+ if (errorFormat != null) {
+ System.err.printf("Error: " + errorFormat, args);
+ System.err.println("");
+ }
+ if (t != null) {
+ System.err.print("Error: " + t.getMessage());
+ }
}
public void warning(String warningFormat, Object... args) {
@@ -165,10 +132,15 @@ class Main {
System.out.println("");
}
}
- });
+
+ public void printf(String msgFormat, Object... args) {
+ System.out.printf(msgFormat, args);
+ }
+ };
+ mSdkManager = SdkManager.createManager(mSdkFolder, mSdkLog);
if (mSdkManager == null) {
- printHelpAndExit("ERROR: Unable to parse SDK content.");
+ mSdkCommandLine.printHelpAndExit("ERROR: Unable to parse SDK content.");
}
}
@@ -176,19 +148,37 @@ class Main {
* Actually do an action...
*/
private void doAction() {
- if (mListObject != null) {
+ String action = mSdkCommandLine.getActionRequested();
+
+ if (SdkCommandLine.ACTION_LIST.equals(action)) {
// list action.
- if (ARG_LIST_TARGET.equals(mListObject)) {
+ if (SdkCommandLine.ARG_TARGET.equals(mSdkCommandLine.getListFilter())) {
displayTargetList();
- } else if (ARG_LIST_VM.equals(mListObject)) {
+ } else if (SdkCommandLine.ARG_VM.equals(mSdkCommandLine.getListFilter())) {
displayVmList();
} else {
- printHelpAndExit("'%s' is not a valid --list option", mListObject);
+ displayTargetList();
+ displayVmList();
}
- } else if (mCreateVm) {
+ } else if (SdkCommandLine.ACTION_NEW_VM.equals(action)) {
createVm();
+ } else if (SdkCommandLine.ACTION_NEW_PROJECT.equals(action)) {
+ // get the target and try to resolve it.
+ int targetId = mSdkCommandLine.getNewProjectTargetId();
+ IAndroidTarget[] targets = mSdkManager.getTargets();
+ if (targetId < 1 || targetId > targets.length) {
+ mSdkCommandLine.printHelpAndExit("ERROR: Wrong target id.");
+ }
+ IAndroidTarget target = targets[targetId - 1];
+
+ ProjectCreator creator = new ProjectCreator(mSdkFolder,
+ OutputLevel.NORMAL, mSdkLog);
+
+ creator.createProject(mSdkCommandLine.getNewProjectLocation(),
+ mSdkCommandLine.getNewProjectName(), mSdkCommandLine.getNewProjectPackage(),
+ mSdkCommandLine.getNewProjectActivity(), target, true);
} else {
- printHelpAndExit(null);
+ mSdkCommandLine.printHelpAndExit(null);
}
}
@@ -274,7 +264,7 @@ class Main {
index++;
}
} catch (AndroidLocationException e) {
- printHelpAndExit(e.getMessage());
+ mSdkCommandLine.printHelpAndExit(e.getMessage());
}
}
@@ -283,11 +273,14 @@ class Main {
*/
private void createVm() {
// find a matching target
- if (mCreateTargetId >= 1 && mCreateTargetId <= mSdkManager.getTargets().length) {
- mCreateTarget = mSdkManager.getTargets()[mCreateTargetId-1]; // target it is 1-based
+ int targetId = mSdkCommandLine.getNewVmTargetId();
+ IAndroidTarget target = null;
+
+ if (targetId >= 1 && targetId <= mSdkManager.getTargets().length) {
+ target = mSdkManager.getTargets()[targetId-1]; // target it is 1-based
} else {
- printHelpAndExit(
- "ERROR: Target Id is not a valid Id. Check android --list target for the list of targets.");
+ mSdkCommandLine.printHelpAndExit(
+ "ERROR: Target Id is not a valid Id. Check 'android list target' for the list of targets.");
}
try {
@@ -295,19 +288,24 @@ class Main {
String vmRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_VMS;
Map<String, String> hardwareConfig = null;
- if (mCreateTarget.isPlatform()) {
+ if (target.isPlatform()) {
try {
- hardwareConfig = promptForHardware(mCreateTarget);
+ hardwareConfig = promptForHardware(target);
} catch (IOException e) {
- printHelpAndExit(e.getMessage());
+ mSdkCommandLine.printHelpAndExit(e.getMessage());
}
}
- VmManager.createVm(vmRoot, mCreateName, mCreateTarget, null /*skinName*/,
- null /*sdcardPath*/, 0 /*sdcardSize*/, hardwareConfig,
+ VmManager.createVm(vmRoot,
+ mSdkCommandLine.getNewVmName(),
+ target,
+ null /*skinName*/,
+ null /*sdcardPath*/,
+ 0 /*sdcardSize*/,
+ hardwareConfig,
null /* sdklog */);
} catch (AndroidLocationException e) {
- printHelpAndExit(e.getMessage());
+ mSdkCommandLine.printHelpAndExit(e.getMessage());
}
}
@@ -325,7 +323,7 @@ class Main {
System.out.print(String.format("Do you which to create a custom hardware profile [%s]",
defaultAnswer));
- result = readLine(readLineBuffer);
+ result = readLine(readLineBuffer).trim();
// handle default:
if (result.length() == 0) {
result = defaultAnswer;
@@ -391,8 +389,7 @@ class Main {
break;
case INTEGER:
try {
- @SuppressWarnings("unused")
- int value = Integer.parseInt(result);
+ Integer.parseInt(result);
map.put(property.getName(), result);
i++; // valid reply, move to next property
} catch (NumberFormatException e) {
@@ -414,9 +411,8 @@ class Main {
}
/**
- * Read the line from the input stream.
+ * Reads the line from the input stream.
* @param buffer
- * @return
* @throws IOException
*/
private String readLine(byte[] buffer) throws IOException {
@@ -434,7 +430,12 @@ class Main {
return new String(buffer, 0, count) + secondHalf;
}
- return new String(buffer, 0, count - 1); // -1 to not include the carriage return
+ // ignore end whitespace
+ while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) {
+ count--;
+ }
+
+ return new String(buffer, 0, count);
}
/**
@@ -442,6 +443,7 @@ class Main {
* @throws IOException If the value is not a boolean string.
*/
private boolean getBooleanReply(String reply) throws IOException {
+
for (String valid : BOOLEAN_YES_REPLIES) {
if (valid.equalsIgnoreCase(reply)) {
return true;
@@ -456,32 +458,4 @@ class Main {
throw new IOException(String.format("%s is not a valid reply", reply));
}
-
- /**
- * Prints the help/usage and exits.
- * @param errorFormat Optional error message to print prior to usage using String.format
- * @param args Arguments for String.format
- */
- private void printHelpAndExit(String errorFormat, Object... args) {
- if (errorFormat != null) {
- System.err.println(String.format(errorFormat, args));
- }
-
- /*
- * usage should fit in 80 columns
- * 12345678901234567890123456789012345678901234567890123456789012345678901234567890
- */
- final String usage = "\n" +
- "Usage:\n" +
- " android --list [target|vm]\n" +
- " android --create --target <target id> --name <name>\n" +
- "\n" +
- "Options:\n" +
- " -l [target|vm], --list [target|vm]\n" +
- " Outputs the available targets or Virtual Machines and their Ids.\n" +
- "\n";
-
- System.out.println(usage);
- System.exit(1);
- }
} \ No newline at end of file
diff --git a/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
new file mode 100644
index 0000000..918c534
--- /dev/null
+++ b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed 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 com.android.sdkmanager;
+
+import com.android.sdklib.SdkManager;
+
+
+/**
+ * Specific command-line flags for the {@link SdkManager}.
+ */
+public class SdkCommandLine extends CommandLineProcessor {
+
+ public static final String ARG_ALIAS = "alias";
+ public static final String ARG_ACTIVITY = "activity";
+ public static final String ARG_VM = "vm";
+ public static final String ARG_TARGET = "target";
+ public static final String ARG_ALL = "all";
+
+ public static final String KEY_IN = "in";
+ public static final String KEY_ACTIVITY = ARG_ACTIVITY;
+ public static final String KEY_PACKAGE = "package";
+ public static final String KEY_MODE = "mode";
+ public static final String KEY_TARGET_ID = ARG_TARGET;
+ public static final String KEY_NAME = "name";
+ public static final String KEY_OUT = "out";
+ public static final String KEY_FILTER = "filter";
+
+ public final static String ACTION_LIST = "list";
+ public final static String ACTION_NEW_VM = ARG_VM;
+ public final static String ACTION_NEW_PROJECT = "project";
+ public final static String ACTION_UPDATE_PROJECT = "update";
+
+ private final static String[][] ACTIONS = {
+ { ACTION_LIST,
+ "Lists existing targets or VMs." },
+ { ACTION_NEW_VM,
+ "Creates a new VM." },
+ { ACTION_NEW_PROJECT,
+ "Creates a new project using a template." },
+ { ACTION_UPDATE_PROJECT,
+ "Updates a new project from existing source (must have an AndroidManifest.xml)." },
+ };
+
+ public SdkCommandLine() {
+ super(ACTIONS);
+
+ define(MODE.ENUM, false, ACTION_LIST, "f", KEY_FILTER,
+ "List filter", new String[] { ARG_ALL, ARG_TARGET, ARG_VM });
+
+ define(MODE.STRING, false, ACTION_NEW_VM, "o", KEY_OUT,
+ "Location path of new VM", null);
+ define(MODE.STRING, true, ACTION_NEW_VM, "n", KEY_NAME,
+ "Name of the new VM", null);
+ define(MODE.INTEGER, true, ACTION_NEW_VM, "t", KEY_TARGET_ID,
+ "Target id of the new VM", null);
+
+ define(MODE.ENUM, true, ACTION_NEW_PROJECT, "m", KEY_MODE,
+ "Project mode", new String[] { ARG_ACTIVITY, ARG_ALIAS });
+ define(MODE.STRING, false, ACTION_NEW_PROJECT, "o", KEY_OUT,
+ "Location path of new project", null);
+ define(MODE.STRING, true, ACTION_NEW_PROJECT, "n", KEY_NAME,
+ "Name of the new project", null);
+ define(MODE.INTEGER, true, ACTION_NEW_PROJECT, "t", KEY_TARGET_ID,
+ "Target id of the new project", null);
+ define(MODE.STRING, true, ACTION_NEW_PROJECT, "p", KEY_PACKAGE,
+ "Package name", null);
+ define(MODE.STRING, true, ACTION_NEW_PROJECT, "a", KEY_ACTIVITY,
+ "Activity name", null);
+
+ define(MODE.STRING, false, ACTION_UPDATE_PROJECT, "i", KEY_IN,
+ "Directory location of the project", null);
+ define(MODE.STRING, true, ACTION_UPDATE_PROJECT, "t", KEY_TARGET_ID,
+ "Target id to set for the project", null);
+ }
+
+ // -- some helpers for list action flags
+
+ /** Helper to retrieve the --filter for the list action. */
+ public String getListFilter() {
+ return ((String) getValue(ACTION_LIST, KEY_FILTER));
+ }
+
+ // -- some helpers for vm action flags
+
+ /** Helper to retrieve the --out location for the new vm action. */
+ public String getNewVmLocation() {
+ return ((String) getValue(ACTION_NEW_VM, KEY_OUT));
+ }
+
+ /** Helper to retrieve the --target id for the new vm action. */
+ public int getNewVmTargetId() {
+ return ((Integer) getValue(ACTION_NEW_VM, KEY_TARGET_ID)).intValue();
+ }
+
+ /** Helper to retrieve the --name for the new vm action. */
+ public String getNewVmName() {
+ return ((String) getValue(ACTION_NEW_VM, KEY_NAME));
+ }
+
+ // -- some helpers for project action flags
+
+ /** Helper to retrieve the --out location for the new project action. */
+ public String getNewProjectLocation() {
+ return ((String) getValue(ACTION_NEW_PROJECT, KEY_OUT));
+ }
+
+ /** Helper to retrieve the --target id for the new project action. */
+ public int getNewProjectTargetId() {
+ return ((Integer) getValue(ACTION_NEW_PROJECT, KEY_TARGET_ID)).intValue();
+ }
+
+ /** Helper to retrieve the --name for the new project action. */
+ public String getNewProjectName() {
+ return ((String) getValue(ACTION_NEW_PROJECT, KEY_NAME));
+ }
+
+ /** Helper to retrieve the --package for the new project action. */
+ public String getNewProjectPackage() {
+ return ((String) getValue(ACTION_NEW_PROJECT, KEY_PACKAGE));
+ }
+
+ /** Helper to retrieve the --activity for the new project action. */
+ public String getNewProjectActivity() {
+ return ((String) getValue(ACTION_NEW_PROJECT, KEY_ACTIVITY));
+ }
+
+ // -- some helpers for update action flags
+
+ /** Helper to retrieve the --out location for the update project action. */
+ public String getUpdateProjectLocation() {
+ return ((String) getValue(ACTION_UPDATE_PROJECT, KEY_OUT));
+ }
+
+ /** Helper to retrieve the --target id for the update project action. */
+ public int getUpdateProjectTargetId() {
+ return ((Integer) getValue(ACTION_UPDATE_PROJECT, KEY_TARGET_ID)).intValue();
+ }
+}
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java b/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java
new file mode 100644
index 0000000..e74cdbd
--- /dev/null
+++ b/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed 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 com.android.sdkmanager;
+
+import junit.framework.TestCase;
+
+
+public class CommandLineProcessorTest extends TestCase {
+
+ /**
+ * A mock version of the {@link CommandLineProcessor} class that does not
+ * exits and captures its stdout/stderr output.
+ */
+ public static class MockCommandLineProcessor extends CommandLineProcessor {
+ private boolean mExitCalled;
+ private boolean mHelpCalled;
+ private String mStdOut = "";
+ private String mStdErr = "";
+
+ public MockCommandLineProcessor() {
+ super(new String[][] {
+ { "action1", "Some action" },
+ { "action2", "Another action" },
+ });
+ define(MODE.STRING, false /*mandatory*/,
+ "action1", "1", "first", "non-mandatory flag", null);
+ define(MODE.STRING, true /*mandatory*/,
+ "action1", "2", "second", "mandatory flag", null);
+ }
+
+ @Override
+ public void printHelpAndExitForAction(String actionFilter,
+ String errorFormat, Object... args) {
+ mHelpCalled = true;
+ super.printHelpAndExitForAction(actionFilter, errorFormat, args);
+ }
+
+ @Override
+ protected void exit() {
+ mExitCalled = true;
+ }
+
+ @Override
+ protected void stdout(String format, Object... args) {
+ String s = String.format(format, args);
+ mStdOut += s + "\n";
+ // don't call super to avoid printing stuff
+ }
+
+ @Override
+ protected void stderr(String format, Object... args) {
+ String s = String.format(format, args);
+ mStdErr += s + "\n";
+ // don't call super to avoid printing stuff
+ }
+
+ public boolean wasHelpCalled() {
+ return mHelpCalled;
+ }
+
+ public boolean wasExitCalled() {
+ return mExitCalled;
+ }
+
+ public String getStdOut() {
+ return mStdOut;
+ }
+
+ public String getStdErr() {
+ return mStdErr;
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public final void testPrintHelpAndExit() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor();
+ assertFalse(c.wasExitCalled());
+ assertFalse(c.wasHelpCalled());
+ assertTrue(c.getStdOut().equals(""));
+ assertTrue(c.getStdErr().equals(""));
+ c.printHelpAndExit(null);
+ assertTrue(c.getStdOut().indexOf("-v") != -1);
+ assertTrue(c.getStdOut().indexOf("--verbose") != -1);
+ assertTrue(c.getStdErr().equals(""));
+ assertTrue(c.wasExitCalled());
+
+ c = new MockCommandLineProcessor();
+ assertFalse(c.wasExitCalled());
+ assertTrue(c.getStdOut().equals(""));
+ assertTrue(c.getStdErr().indexOf("Missing parameter") == -1);
+
+ c.printHelpAndExit("Missing %s", "parameter");
+ assertTrue(c.wasExitCalled());
+ assertFalse(c.getStdOut().equals(""));
+ assertTrue(c.getStdErr().indexOf("Missing parameter") != -1);
+ }
+
+ public final void testVerbose() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor();
+
+ assertFalse(c.isVerbose());
+ c.parseArgs(new String[] { "-v" });
+ assertTrue(c.isVerbose());
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing action name.") != -1);
+
+ c = new MockCommandLineProcessor();
+ c.parseArgs(new String[] { "--verbose" });
+ assertTrue(c.isVerbose());
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing action name.") != -1);
+ }
+
+ public final void testHelp() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor();
+
+ c.parseArgs(new String[] { "-h" });
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing action name.") == -1);
+
+ c = new MockCommandLineProcessor();
+ c.parseArgs(new String[] { "--help" });
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing action name.") == -1);
+ }
+
+ public final void testMandatory() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor();
+
+ c.parseArgs(new String[] { "action1", "-1", "value1", "-2", "value2" });
+ assertFalse(c.wasExitCalled());
+ assertFalse(c.wasHelpCalled());
+ assertEquals("", c.getStdErr());
+ assertEquals("value1", c.getValue("action1", "first"));
+ assertEquals("value2", c.getValue("action1", "second"));
+
+ c = new MockCommandLineProcessor();
+ c.parseArgs(new String[] { "action1", "-2", "value2" });
+ assertFalse(c.wasExitCalled());
+ assertFalse(c.wasHelpCalled());
+ assertEquals("", c.getStdErr());
+ assertEquals(null, c.getValue("action1", "first"));
+ assertEquals("value2", c.getValue("action1", "second"));
+
+ c = new MockCommandLineProcessor();
+ c.parseArgs(new String[] { "action1" });
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("must be defined") != -1);
+ assertEquals(null, c.getValue("action1", "first"));
+ assertEquals(null, c.getValue("action1", "second"));
+ }
+}
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java b/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java
new file mode 100644
index 0000000..b943b98
--- /dev/null
+++ b/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed 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 com.android.sdkmanager;
+
+import junit.framework.TestCase;
+
+public class SdkCommandLineTest extends TestCase {
+
+ /**
+ * A mock version of the {@link SdkCommandLine} class that does not
+ * exits and discards its stdout/stderr output.
+ */
+ public static class MockSdkCommandLine extends SdkCommandLine {
+ private boolean mExitCalled;
+ private boolean mHelpCalled;
+
+ public MockSdkCommandLine() {
+ }
+
+ @Override
+ public void printHelpAndExitForAction(String actionFilter,
+ String errorFormat, Object... args) {
+ mHelpCalled = true;
+ super.printHelpAndExitForAction(actionFilter, errorFormat, args);
+ }
+
+ @Override
+ protected void exit() {
+ mExitCalled = true;
+ }
+
+ @Override
+ protected void stdout(String format, Object... args) {
+ // discard
+ }
+
+ @Override
+ protected void stderr(String format, Object... args) {
+ // discard
+ }
+
+ public boolean wasExitCalled() {
+ return mExitCalled;
+ }
+
+ public boolean wasHelpCalled() {
+ return mHelpCalled;
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /** Test list with long name and verbose */
+ public final void testList_Long_Verbose() {
+ MockSdkCommandLine c = new MockSdkCommandLine();
+ assertEquals("all", c.getListFilter());
+ c.parseArgs(new String[] { "-v", "list", "--filter", "vm" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("vm", c.getListFilter());
+ assertTrue(c.isVerbose());
+ }
+
+ /** Test list with short name and no verbose */
+ public final void testList_Short() {
+ MockSdkCommandLine c = new MockSdkCommandLine();
+ assertEquals("all", c.getListFilter());
+ c.parseArgs(new String[] { "list", "-f", "vm" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("vm", c.getListFilter());
+ }
+
+ /** Test list with long name and missing parameter */
+ public final void testList_Long_MissingParam() {
+ MockSdkCommandLine c = new MockSdkCommandLine();
+ assertEquals("all", c.getListFilter());
+ c.parseArgs(new String[] { "list", "--filter" });
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.wasExitCalled());
+ assertEquals("all", c.getListFilter());
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
index 5759613..2a2efe7 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
@@ -95,6 +95,10 @@ final class AddOnTarget implements IAndroidTarget {
}
}
+ public String getLocation() {
+ return mLocation;
+ }
+
public String getName() {
return mName;
}
@@ -103,6 +107,10 @@ final class AddOnTarget implements IAndroidTarget {
return mVendor;
}
+ public String getFullName() {
+ return String.format("%1$s (%2$s)", mName, mVendor);
+ }
+
public String getDescription() {
return mDescription;
}
@@ -140,6 +148,28 @@ final class AddOnTarget implements IAndroidTarget {
return mLibraries;
}
+ public boolean isCompatibleBaseFor(IAndroidTarget target) {
+ // basic test
+ if (target == this) {
+ return true;
+ }
+
+ // if the receiver has no optional library, then anything with api version number >= to
+ // the receiver is compatible.
+ if (mLibraries.length == 0) {
+ return target.getApiVersionNumber() >= getApiVersionNumber();
+ }
+
+ // Otherwise, target is only compatible if the vendor and name are equals with the api
+ // number greater or equal (ie target is a newer version of this add-on).
+ if (target.isPlatform() == false) {
+ return (mVendor.equals(target.getVendor()) && mName.equals(target.getName()) &&
+ target.getApiVersionNumber() >= getApiVersionNumber());
+ }
+
+ return false;
+ }
+
public String hashString() {
return String.format(ADD_ON_FORMAT, mVendor, mName, mBasePlatform.getApiVersionNumber());
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
index e5d45b2..0e2b109 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
@@ -22,23 +22,41 @@ package com.android.sdklib;
*/
public interface IAndroidTarget extends Comparable<IAndroidTarget> {
+ /** OS Path to the "android.jar" file. */
public static int ANDROID_JAR = 1;
+ /** OS Path to the "framework.aidl" file. */
public static int ANDROID_AIDL = 2;
+ /** OS Path to "images" folder which contains the emulator system images. */
public static int IMAGES = 3;
+ /** OS Path to the "samples" folder which contains sample projects. */
public static int SAMPLES = 4;
+ /** OS Path to the "skins" folder which contains the emulator skins. */
public static int SKINS = 5;
+ /** OS Path to the "templates" folder which contains the templates for new projects. */
public static int TEMPLATES = 6;
+ /** OS Path to the "data" folder which contains data & libraries for the SDK tools. */
public static int DATA = 7;
+ /** OS Path to the "attrs.xml" file. */
public static int ATTRIBUTES = 8;
+ /** OS Path to the "attrs_manifest.xml" file. */
public static int MANIFEST_ATTRIBUTES = 9;
+ /** OS Path to the "data/layoutlib.jar" library. */
public static int LAYOUT_LIB = 10;
+ /** OS Path to the "data/res" folder. */
public static int RESOURCES = 11;
+ /** OS Path to the "data/fonts" folder. */
public static int FONTS = 12;
+ /** OS Path to the "data/widgets.txt" file. */
public static int WIDGETS = 13;
+ /** OS Path to the "data/activity_actions.txt" file. */
public static int ACTIONS_ACTIVITY = 14;
+ /** OS Path to the "data/broadcast_actions.txt" file. */
public static int ACTIONS_BROADCAST = 15;
+ /** OS Path to the "data/service_actions.txt" file. */
public static int ACTIONS_SERVICE = 16;
+ /** OS Path to the "data/categories.txt" file. */
public static int CATEGORIES = 17;
+ /** OS Path to the "sources" folder. */
public static int SOURCES = 18;
public interface IOptionalLibrary {
@@ -48,6 +66,11 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> {
}
/**
+ * Returns the target location.
+ */
+ String getLocation();
+
+ /**
* Returns the name of the vendor of the target.
*/
String getVendor();
@@ -58,6 +81,12 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> {
String getName();
/**
+ * Returns the full name of the target, possibly including vendor name.
+ * @return
+ */
+ String getFullName();
+
+ /**
* Returns the description of the target.
*/
String getDescription();
@@ -80,7 +109,7 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> {
/**
* Returns the path of a platform component.
* @param pathId the id representing the path to return. Any of the constants defined in the
- * {@link ITargetDataProvider} interface can be used.
+ * {@link IAndroidTarget} interface can be used.
*/
String getPath(int pathId);
@@ -96,6 +125,15 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> {
IOptionalLibrary[] getOptionalLibraries();
/**
+ * Returns whether the given target is compatible with the receiver.
+ * <p/>A target is considered compatible if applications developed for the receiver can run on
+ * the given target.
+ *
+ * @param target the IAndroidTarget to test.
+ */
+ boolean isCompatibleBaseFor(IAndroidTarget target);
+
+ /**
* Returns a string able to uniquely identify a target.
* Typically the target will encode information such as api level, whether it's a platform
* or add-on, and if it's an add-on vendor and add-on name.
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java
index 3eda37f..8cbe44a 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java
@@ -21,5 +21,6 @@ package com.android.sdklib;
*/
public interface ISdkLog {
void warning(String warningFormat, Object... args);
- void error(String errorFormat, Object... args);
+ void error(Throwable t, String errorFormat, Object... args);
+ void printf(String msgFormat, Object... args);
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
index f5a1f6d..59fa81c 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
@@ -95,6 +95,10 @@ final class PlatformTarget implements IAndroidTarget {
public String getName() {
return mName;
}
+
+ public String getFullName() {
+ return mName;
+ }
/*
* (non-Javadoc)
@@ -136,7 +140,17 @@ final class PlatformTarget implements IAndroidTarget {
public IOptionalLibrary[] getOptionalLibraries() {
return null;
}
+
+ public boolean isCompatibleBaseFor(IAndroidTarget target) {
+ // basic test
+ if (target == this) {
+ return true;
+ }
+ // target is compatible wit the receiver as long as its api version number is greater or
+ // equal.
+ return target.getApiVersionNumber() >= mApiVersionNumber;
+ }
public String hashString() {
return String.format(PLATFORM_HASH, mApiVersionNumber);
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
index 78d1fda..ede0d86 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
@@ -64,6 +64,40 @@ public final class SdkConstants {
/** Skin layout file */
public final static String FN_SKIN_LAYOUT = "layout";//$NON-NLS-1$
+ /* Folder Names for Android Projects . */
+
+ /** Resources folder name, i.e. "res". */
+ public final static String FD_RESOURCES = "res"; //$NON-NLS-1$
+ /** Assets folder name, i.e. "assets" */
+ public final static String FD_ASSETS = "assets"; //$NON-NLS-1$
+ /** Default source folder name, i.e. "src" */
+ public final static String FD_SOURCES = "src"; //$NON-NLS-1$
+ /** Default native library folder name inside the project, i.e. "libs"
+ * While the folder inside the .apk is "lib", we call that one libs because
+ * that's what we use in ant for both .jar and .so and we need to make the 2 development ways
+ * compatible. */
+ public final static String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$
+ /** Native lib folder inside the APK: "lib" */
+ public final static String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$
+ /** Default output folder name, i.e. "bin" */
+ public final static String FD_OUTPUT = "bin"; //$NON-NLS-1$
+ /** Default anim resource folder name, i.e. "anim" */
+ public final static String FD_ANIM = "anim"; //$NON-NLS-1$
+ /** Default color resource folder name, i.e. "color" */
+ public final static String FD_COLOR = "color"; //$NON-NLS-1$
+ /** Default drawable resource folder name, i.e. "drawable" */
+ public final static String FD_DRAWABLE = "drawable"; //$NON-NLS-1$
+ /** Default layout resource folder name, i.e. "layout" */
+ public final static String FD_LAYOUT = "layout"; //$NON-NLS-1$
+ /** Default menu resource folder name, i.e. "menu" */
+ public final static String FD_MENU = "menu"; //$NON-NLS-1$
+ /** Default values resource folder name, i.e. "values" */
+ public final static String FD_VALUES = "values"; //$NON-NLS-1$
+ /** Default xml resource folder name, i.e. "xml" */
+ public final static String FD_XML = "xml"; //$NON-NLS-1$
+ /** Default raw resource folder name, i.e. "raw" */
+ public final static String FD_RAW = "raw"; //$NON-NLS-1$
+
/* Folder Names for the Android SDK */
/** Name of the SDK platforms folder. */
@@ -90,13 +124,12 @@ public final class SdkConstants {
public final static String FD_RES = "res";
/** Name of the SDK font folder, i.e. "fonts" */
public final static String FD_FONTS = "fonts";
- /** Default values resource folder name, i.e. "values" */
- public final static String FD_VALUES = "values";
/** Name of the android sources directory */
public static final String FD_ANDROID_SOURCES = "sources";
/** Name of the addon libs folder. */
public final static String FD_ADDON_LIBS = "libs";
+
/* Folder path relative to the SDK root */
/** Path of the documentation directory relative to the sdk folder.
* This is an OS path, ending with a separator. */
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
index 67b8499..b4de51a 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
@@ -36,8 +36,8 @@ import java.util.regex.Pattern;
*/
public final class SdkManager {
- private final static String PROP_VERSION_SDK = "ro.build.version.sdk";
- private final static String PROP_VERSION_RELEASE = "ro.build.version.release";
+ public final static String PROP_VERSION_SDK = "ro.build.version.sdk";
+ public final static String PROP_VERSION_RELEASE = "ro.build.version.release";
private final static String ADDON_NAME = "name";
private final static String ADDON_VENDOR = "vendor";
@@ -73,7 +73,7 @@ public final class SdkManager {
return manager;
} catch (IllegalArgumentException e) {
if (log != null) {
- log.error(e.getMessage());
+ log.error(e, "Error parsing the sdk.");
}
}
@@ -188,13 +188,14 @@ public final class SdkManager {
// looks like apiNumber does not parse to a number.
// Ignore this platform.
if (log != null) {
- log.error("Ignoring platform '%1$s': %2$s is not a valid number in %3$s.",
+ log.error(null,
+ "Ignoring platform '%1$s': %2$s is not a valid number in %3$s.",
platform.getName(), PROP_VERSION_SDK, SdkConstants.FN_BUILD_PROP);
}
}
}
} else if (log != null) {
- log.error("Ignoring platform '%1$s': %2$s is missing.", platform.getName(),
+ log.error(null, "Ignoring platform '%1$s': %2$s is missing.", platform.getName(),
SdkConstants.FN_BUILD_PROP);
}
@@ -281,7 +282,7 @@ public final class SdkManager {
if (baseTarget == null) {
if (log != null) {
- log.error(
+ log.error(null,
"Ignoring add-on '%1$s': Unable to find base platform with API level %2$d",
addon.getName(), apiValue);
}
@@ -292,7 +293,7 @@ public final class SdkManager {
// looks like apiNumber does not parse to a number.
// Ignore this add-on.
if (log != null) {
- log.error(
+ log.error(null,
"Ignoring add-on '%1$s': %2$s is not a valid number in %3$s.",
addon.getName(), ADDON_API, SdkConstants.FN_BUILD_PROP);
}
@@ -331,7 +332,7 @@ public final class SdkManager {
return target;
}
} else if (log != null) {
- log.error("Ignoring add-on '%1$s': %2$s is missing.", addon.getName(),
+ log.error(null, "Ignoring add-on '%1$s': %2$s is missing.", addon.getName(),
SdkConstants.FN_MANIFEST_INI);
}
@@ -340,7 +341,7 @@ public final class SdkManager {
private void displayAddonManifestError(ISdkLog log, String addonName, String valueName) {
if (log != null) {
- log.error("Ignoring add-on '%1$s': '%2$s' is missing from %3$s.",
+ log.error(null, "Ignoring add-on '%1$s': '%2$s' is missing from %3$s.",
addonName, valueName, SdkConstants.FN_MANIFEST_INI);
}
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java
new file mode 100644
index 0000000..1184fc2
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed 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 com.android.sdklib.project;
+
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISdkLog;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.project.ProjectProperties.PropertyType;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Creates the basic files needed to get an Android project up and running. Also
+ * allows creation of IntelliJ project files.
+ *
+ * @hide
+ */
+public class ProjectCreator {
+
+ private final static String PH_JAVA_FOLDER = "PACKAGE_PATH";
+ private final static String PH_PACKAGE = "PACKAGE";
+ private final static String PH_ACTIVITY_NAME = "ACTIVITY_NAME";
+
+ private final static String FOLDER_TESTS = "tests";
+
+ public enum OutputLevel {
+ SILENT, NORMAL, VERBOSE;
+ }
+
+ private static class ProjectCreateException extends Exception {
+ /** default UID. This will not be serialized anyway. */
+ private static final long serialVersionUID = 1L;
+
+ ProjectCreateException(String message) {
+ super(message);
+ }
+
+ ProjectCreateException(Throwable t, String format, Object... args) {
+ super(format != null ? String.format(format, args) : format, t);
+ }
+
+ ProjectCreateException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+ }
+
+ private final OutputLevel mLevel;
+
+ private final ISdkLog mLog;
+ private final String mSdkFolder;
+
+ public ProjectCreator(String sdkFolder, OutputLevel level, ISdkLog log) {
+ mSdkFolder = sdkFolder;
+ mLevel = level;
+ mLog = log;
+ }
+
+ /**
+ * Creates a new project.
+ * @param folderPath the folder of the project to create. This folder must exist.
+ * @param projectName the name of the project.
+ * @param packageName the package of the project.
+ * @param activityName the activity of the project as it will appear in the manifest.
+ * @param target the project target.
+ * @param isTestProject whether the project to create is a test project.
+ */
+ public void createProject(String folderPath, String projectName,
+ String packageName, String activityName, IAndroidTarget target,
+ boolean isTestProject) {
+
+ // check project folder exists.
+ File projectFolder = new File(folderPath);
+ if (projectFolder.isDirectory() == false) {
+ mLog.error(null, "Folder '%s' does not exist. Aborting...", folderPath);
+ return;
+ }
+
+ try {
+ // first create the project properties.
+
+ // location of the SDK goes in localProperty
+ ProjectProperties localProperties = ProjectProperties.create(folderPath,
+ PropertyType.LOCAL);
+ localProperties.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
+ localProperties.save();
+
+ // target goes in default properties
+ ProjectProperties defaultProperties = ProjectProperties.create(folderPath,
+ PropertyType.DEFAULT);
+ defaultProperties.setAndroidTarget(target);
+ defaultProperties.save();
+
+ // create the map for place-holders of values to replace in the templates
+ final HashMap<String, String> keywords = new HashMap<String, String>();
+
+ // create the required folders.
+ // compute src folder path
+ final String packagePath =
+ stripString(packageName.replace(".", File.separator),
+ File.separatorChar);
+
+ // put this path in the place-holder map for project files that needs to list
+ // files manually.
+ keywords.put(PH_JAVA_FOLDER, packagePath);
+
+ keywords.put(PH_PACKAGE, packageName);
+ if (activityName != null) {
+ keywords.put(PH_ACTIVITY_NAME, activityName);
+ }
+
+ // create the source folder and the java package folders.
+ final String srcFolderPath = SdkConstants.FD_SOURCES + File.separator + packagePath;
+ File sourceFolder = createDirs(projectFolder, srcFolderPath);
+ String javaTemplate = "java_file.template";
+ String activityFileName = activityName + ".java";
+ if (isTestProject) {
+ javaTemplate = "java_tests_file.template";
+ activityFileName = activityName + "Test.java";
+ }
+ installTemplate(javaTemplate, new File(sourceFolder, activityFileName),
+ keywords, target);
+
+ // create other useful folders
+ File resourceFodler = createDirs(projectFolder, SdkConstants.FD_RESOURCES);
+ createDirs(projectFolder, SdkConstants.FD_OUTPUT);
+ createDirs(projectFolder, SdkConstants.FD_NATIVE_LIBS);
+
+ if (isTestProject == false) {
+ /* Make res files only for non test projects */
+ File valueFolder = createDirs(resourceFodler, SdkConstants.FD_VALUES);
+ installTemplate("strings.template", new File(valueFolder, "strings.xml"),
+ keywords, target);
+
+ File layoutFolder = createDirs(resourceFodler, SdkConstants.FD_LAYOUT);
+ installTemplate("layout.template", new File(layoutFolder, "main.xml"),
+ keywords, target);
+ }
+
+ /* Make AndroidManifest.xml and build.xml files */
+ String manifestTemplate = "AndroidManifest.template";
+ if (isTestProject) {
+ manifestTemplate = "AndroidManifest.tests.template";
+ }
+
+ installTemplate(manifestTemplate, new File(projectFolder, "AndroidManifest.xml"),
+ keywords, target);
+
+ installTemplate("build.template", new File(projectFolder, "build.xml"), keywords);
+
+ // if this is not a test project, then we create one.
+ if (isTestProject == false) {
+ // create the test project folder.
+ createDirs(projectFolder, FOLDER_TESTS);
+ File testProjectFolder = new File(folderPath, FOLDER_TESTS);
+
+ createProject(testProjectFolder.getAbsolutePath(), projectName, packageName,
+ activityName, target, true /*isTestProject*/);
+ }
+ } catch (ProjectCreateException e) {
+ mLog.error(e, null);
+ } catch (IOException e) {
+ mLog.error(e, null);
+ }
+ }
+
+ /**
+ * Installs a new file that is based on a template file provided by a given target.
+ * Each match of each key from the place-holder map in the template will be replaced with its
+ * corresponding value in the created file.
+ *
+ * @param templateName the name of to the template file
+ * @param dest the path to the destination file, relative to the project
+ * @param placeholderMap a map of (place-holder, value) to create the file from the template.
+ * @param target the Target of the project that will be providing the template.
+ * @throws ProjectCreateException
+ */
+ private void installTemplate(String templateName, File destFile,
+ Map<String, String> placeholderMap, IAndroidTarget target)
+ throws ProjectCreateException {
+ // query the target for its template directory
+ String templateFolder = target.getPath(IAndroidTarget.TEMPLATES);
+ final String sourcePath = templateFolder + File.separator + templateName;
+
+ installFullPathTemplate(sourcePath, destFile, placeholderMap);
+ }
+
+ /**
+ * Installs a new file that is based on a template file provided by the tools folder.
+ * Each match of each key from the place-holder map in the template will be replaced with its
+ * corresponding value in the created file.
+ *
+ * @param templateName the name of to the template file
+ * @param dest the path to the destination file, relative to the project
+ * @param placeholderMap a map of (place-holder, value) to create the file from the template.
+ * @throws ProjectCreateException
+ */
+ private void installTemplate(String templateName, File destFile,
+ Map<String, String> placeholderMap)
+ throws ProjectCreateException {
+ // query the target for its template directory
+ String templateFolder = mSdkFolder + File.separator + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
+ final String sourcePath = templateFolder + File.separator + templateName;
+
+ installFullPathTemplate(sourcePath, destFile, placeholderMap);
+ }
+
+ /**
+ * Installs a new file that is based on a template.
+ * Each match of each key from the place-holder map in the template will be replaced with its
+ * corresponding value in the created file.
+ *
+ * @param sourcePath the full path to the source template file
+ * @param destFile the destination file
+ * @param placeholderMap a map of (place-holder, value) to create the file from the template.
+ * @throws ProjectCreateException
+ */
+ private void installFullPathTemplate(String sourcePath, File destFile,
+ Map<String, String> placeholderMap) throws ProjectCreateException {
+ try {
+ BufferedWriter out = new BufferedWriter(new FileWriter(destFile));
+ BufferedReader in = new BufferedReader(new FileReader(sourcePath));
+ String line;
+
+ while ((line = in.readLine()) != null) {
+ for (String key : placeholderMap.keySet()) {
+ line = line.replace(key, placeholderMap.get(key));
+ }
+
+ out.write(line);
+ out.newLine();
+ }
+
+ out.close();
+ in.close();
+ } catch (Exception e) {
+ throw new ProjectCreateException(e, "Could not access %1$s: %2$s",
+ destFile, e.getMessage());
+ }
+
+ println("Added file %1$s", destFile);
+ }
+
+
+ /**
+ * Prints a message unless silence is enabled.
+ * @param format Format for String.format
+ * @param args Arguments for String.format
+ */
+ private void println(String format, Object... args) {
+ if (mLevel == OutputLevel.VERBOSE) {
+ System.out.println(String.format(format, args));
+ }
+ }
+
+ /**
+ * Creates a new folder, along with any parent folders that do not exists.
+ *
+ * @param parent the parent folder
+ * @param name the name of the directory to create.
+ * @throws ProjectCreateException
+ */
+ private File createDirs(File parent, String name) throws ProjectCreateException {
+ final File newFolder = new File(parent, name);
+ boolean existedBefore = true;
+
+ if (!newFolder.exists()) {
+ if (!newFolder.mkdirs()) {
+ throw new ProjectCreateException("Could not create directory: %1$s", newFolder);
+ }
+ existedBefore = false;
+ }
+
+ if (newFolder.isDirectory()) {
+ if (!newFolder.canWrite()) {
+ throw new ProjectCreateException("Path is not writable: %1$s", newFolder);
+ }
+ } else {
+ throw new ProjectCreateException("Path is not a directory: %1$s", newFolder);
+ }
+
+ if (!existedBefore) {
+ try {
+ println("Created directory %1$s", newFolder.getCanonicalPath());
+ } catch (IOException e) {
+ throw new ProjectCreateException(
+ "Could not determine canonical path of created directory", e);
+ }
+ }
+
+ return newFolder;
+ }
+
+ /**
+ * Strips the string of beginning and trailing characters (multiple
+ * characters will be stripped, example stripString("..test...", '.')
+ * results in "test";
+ *
+ * @param s the string to strip
+ * @param strip the character to strip from beginning and end
+ * @return the stripped string or the empty string if everything is stripped.
+ */
+ private static String stripString(String s, char strip) {
+ final int sLen = s.length();
+ int newStart = 0, newEnd = sLen - 1;
+
+ while (newStart < sLen && s.charAt(newStart) == strip) {
+ newStart++;
+ }
+ while (newEnd >= 0 && s.charAt(newEnd) == strip) {
+ newEnd--;
+ }
+
+ /*
+ * newEnd contains a char we want, and substring takes end as being
+ * exclusive
+ */
+ newEnd++;
+
+ if (newStart >= sLen || newEnd < 0) {
+ return "";
+ }
+
+ return s.substring(newStart, newEnd);
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java
index c0c1fe3..473f284 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java
@@ -16,6 +16,7 @@
package com.android.sdklib.project;
+import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkManager;
import java.io.File;
@@ -32,38 +33,87 @@ import java.util.Map.Entry;
public final class ProjectProperties {
/** The property name for the project target */
public final static String PROPERTY_TARGET = "target";
- public final static String PROPERTY_SDK = "sdk-folder";
+ public final static String PROPERTY_SDK = "sdk-location";
- private final static String PROPERTIES_FILE = "default.properties";
+ public static enum PropertyType {
+ BUILD("build.properties", BUILD_HEADER),
+ DEFAULT("default.properties", DEFAULT_HEADER),
+ LOCAL("local.properties", LOCAL_HEADER);
+
+ private final String mFilename;
+ private final String mHeader;
+
+ PropertyType(String filename, String header) {
+ mFilename = filename;
+ mHeader = header;
+ }
+ }
- private final static String PROP_HEADER =
+ private final static String LOCAL_HEADER =
+// 1-------10--------20--------30--------40--------50--------60--------70--------80
"# This file is automatically generated by Android Tools.\n" +
"# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" +
- "# For customized properties when using Ant, set new values\n" +
- "# in a \"build.properties\" file.\n\n";
+ "# \n" +
+ "# This file must *NOT* be checked in Version Control Systems,\n" +
+ "# as it contains information specific to your local configuration.\n" +
+ "\n";
+
+ private final static String DEFAULT_HEADER =
+// 1-------10--------20--------30--------40--------50--------60--------70--------80
+ "# This file is automatically generated by Android Tools.\n" +
+ "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" +
+ "# \n" +
+ "# This file must be checked in Version Control Systems.\n" +
+ "# \n" +
+ "# To customize properties used by the Ant build system use,\n" +
+ "# \"build.properties\", and override values to adapt the script to your" +
+ "# project structure.\n" +
+ "\n";
+
+ private final static String BUILD_HEADER =
+// 1-------10--------20--------30--------40--------50--------60--------70--------80
+ "# This file is used to override default values used by the Ant build system.\n" +
+ "# \n" +
+ "# This file must be checked in Version Control Systems, as it is" +
+ "# integral to the build system of your project.\n" +
+ "# \n" +
+ "# Use this file to change values like:\n" +
+ "# application-package\n:" +
+ "# the name of your application package as defined in the manifest.\n" +
+ "# Used by the 'uninstall' rule.\n"+
+ "# source-folder\n:" +
+ "# the name of the source folder. Default is 'src'.\n" +
+ "# out-folder\n:" +
+ "# the name of the output folder. Default is 'bin'\n" +
+ "\n";
private final static Map<String, String> COMMENT_MAP = new HashMap<String, String>();
static {
- COMMENT_MAP.put(PROPERTY_TARGET, "# Project target.\n");
- COMMENT_MAP.put(PROPERTY_SDK, "# location of the SDK. Only used by Ant.\n");
+// 1-------10--------20--------30--------40--------50--------60--------70--------80
+ COMMENT_MAP.put(PROPERTY_TARGET,
+ "# Project target.\n");
+ COMMENT_MAP.put(PROPERTY_SDK, "# location of the SDK. This is only used by Ant\n" +
+ "# For customization when using a Version Control System, please read the\n" +
+ "# header note.\n");
}
private final String mProjectFolderOsPath;
private final Map<String, String> mProperties;
+ private final PropertyType mType;
/**
* Loads a project properties file and return a {@link ProjectProperties} object
* containing the properties
* @param projectFolderOsPath the project folder.
*/
- public static ProjectProperties load(String projectFolderOsPath) {
+ public static ProjectProperties load(String projectFolderOsPath, PropertyType type) {
File projectFolder = new File(projectFolderOsPath);
if (projectFolder.isDirectory()) {
- File defaultFile = new File(projectFolder, PROPERTIES_FILE);
+ File defaultFile = new File(projectFolder, type.mFilename);
if (defaultFile.isFile()) {
Map<String, String> map = SdkManager.parsePropertyFile(defaultFile, null /* log */);
if (map != null) {
- return new ProjectProperties(projectFolderOsPath, map);
+ return new ProjectProperties(projectFolderOsPath, map, type);
}
}
}
@@ -71,13 +121,14 @@ public final class ProjectProperties {
}
/**
- * Creates a new project properties file, with no properties.
+ * Creates a new project properties object, with no properties.
* <p/>The file is not created until {@link #save()} is called.
* @param projectFolderOsPath the project folder.
+ * @param type
*/
- public static ProjectProperties create(String projectFolderOsPath) {
+ public static ProjectProperties create(String projectFolderOsPath, PropertyType type) {
// create and return a ProjectProperties with an empty map.
- return new ProjectProperties(projectFolderOsPath, new HashMap<String, String>());
+ return new ProjectProperties(projectFolderOsPath, new HashMap<String, String>(), type);
}
/**
@@ -90,6 +141,15 @@ public final class ProjectProperties {
}
/**
+ * Sets the target property to the given {@link IAndroidTarget} object.
+ * @param target the Android target.
+ */
+ public void setAndroidTarget(IAndroidTarget target) {
+ assert mType == PropertyType.DEFAULT;
+ mProperties.put(PROPERTY_TARGET, target.hashString());
+ }
+
+ /**
* Returns the value of a property.
* @param name the name of the property.
* @return the property value or null if the property is not set.
@@ -103,12 +163,12 @@ public final class ProjectProperties {
* @throws IOException
*/
public void save() throws IOException {
- File toSave = new File(mProjectFolderOsPath, PROPERTIES_FILE);
+ File toSave = new File(mProjectFolderOsPath, mType.mFilename);
FileWriter writer = new FileWriter(toSave);
// write the header
- writer.write(PROP_HEADER);
+ writer.write(mType.mHeader);
// write the properties.
for (Entry<String, String> entry : mProperties.entrySet()) {
@@ -128,9 +188,12 @@ public final class ProjectProperties {
* Use {@link #load(String)} or {@link #create(String)} to instantiate.
* @param projectFolderOsPath
* @param map
+ * @param type
*/
- private ProjectProperties(String projectFolderOsPath, Map<String, String> map) {
+ private ProjectProperties(String projectFolderOsPath, Map<String, String> map,
+ PropertyType type) {
mProjectFolderOsPath = projectFolderOsPath;
mProperties = map;
+ mType = type;
}
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java
index a9f1b17..a28561d 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java
@@ -76,9 +76,27 @@ public final class VmManager {
buildVmList(sdk);
}
+ /**
+ * Returns the existing VMs.
+ * @return a newly allocated arrays containing all the VMs.
+ */
public VmInfo[] getVms() {
return mVmList.toArray(new VmInfo[mVmList.size()]);
}
+
+ /**
+ * Returns the {@link VmInfo} matching the given <var>name</var>.
+ * @return the matching VmInfo or <code>null</code> if none were found.
+ */
+ public VmInfo getVm(String name) {
+ for (VmInfo info : mVmList) {
+ if (info.name.equals(name)) {
+ return info;
+ }
+ }
+
+ return null;
+ }
/**
* Creates a new VM.
@@ -101,7 +119,7 @@ public final class VmManager {
File rootDirectory = new File(parentFolder);
if (rootDirectory.isDirectory() == false) {
if (log != null) {
- log.error("%s does not exists.", parentFolder);
+ log.error(null, "%s does not exists.", parentFolder);
}
return;
}
@@ -109,7 +127,7 @@ public final class VmManager {
File vmFolder = new File(parentFolder, name + ".avm");
if (vmFolder.exists()) {
if (log != null) {
- log.error("%s already exists.", vmFolder.getAbsolutePath());
+ log.error(null, "%s already exists.", vmFolder.getAbsolutePath());
}
return;
}
diff --git a/sdkmanager/libs/sdkuilib/.classpath b/sdkmanager/libs/sdkuilib/.classpath
new file mode 100644
index 0000000..eb5af7e
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sdkmanager/libs/sdkuilib/.project b/sdkmanager/libs/sdkuilib/.project
new file mode 100644
index 0000000..da430c8
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>SdkUiLib</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
index ddc492e..fc951f2 100644
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
@@ -40,6 +40,11 @@ import java.util.ArrayList;
/**
* The SDK target selector is a table that is added to the given parent composite.
+ * <p/>
+ * To use, create it using {@link #SdkTargetSelector(Composite, IAndroidTarget[], boolean)} then
+ * call {@link #setSelection(IAndroidTarget)}, {@link #setSelectionListener(SelectionListener)}
+ * and finally use {@link #getFirstSelected()} or {@link #getAllSelected()} to retrieve the
+ * selection.
*/
public class SdkTargetSelector {
@@ -49,6 +54,14 @@ public class SdkTargetSelector {
private Table mTable;
private Label mDescription;
+ /**
+ * Creates a new SDK Target Selector.
+ *
+ * @param parent The parent composite where the selector will be added.
+ * @param targets The list of targets. This is <em>not</em> copied, the caller must not modify.
+ * @param allowMultipleSelection True if more than one SDK target can be selected at the same
+ * time.
+ */
public SdkTargetSelector(Composite parent, IAndroidTarget[] targets,
boolean allowMultipleSelection) {
mTargets = targets;
@@ -81,14 +94,25 @@ public class SdkTargetSelector {
column1.setText("Vendor");
final TableColumn column2 = new TableColumn(mTable, SWT.NONE);
column2.setText("API Level");
+ final TableColumn column3 = new TableColumn(mTable, SWT.NONE);
+ column3.setText("SDK");
- adjustColumnsWidth(mTable, column0, column1, column2);
+ adjustColumnsWidth(mTable, column0, column1, column2, column3);
setupSelectionListener(mTable);
fillTable(mTable);
setupTooltip(mTable);
}
/**
+ * Returns the list of known targets.
+ * <p/>
+ * This is not a copy. Callers must <em>not</em> modify this array.
+ */
+ public IAndroidTarget[] getTargets() {
+ return mTargets;
+ }
+
+ /**
* Sets a selection listener. Set it to null to remove it.
* The listener will be called <em>after</em> this table processed its selection
* events so that the caller can see the updated state.
@@ -107,20 +131,33 @@ public class SdkTargetSelector {
/**
* Sets the current target selection.
+ * <p/>
+ * If the selection is actually changed, this will invoke the selection listener
+ * (if any) with a null event.
+ *
* @param target the target to be selection
* @return true if the target could be selected, false otherwise.
*/
public boolean setSelection(IAndroidTarget target) {
boolean found = false;
+ boolean modified = false;
for (TableItem i : mTable.getItems()) {
if ((IAndroidTarget) i.getData() == target) {
found = true;
- i.setChecked(true);
- } else {
+ if (!i.getChecked()) {
+ modified = true;
+ i.setChecked(true);
+ }
+ } else if (i.getChecked()) {
+ modified = true;
i.setChecked(false);
}
}
+ if (modified && mSelectionListener != null) {
+ mSelectionListener.widgetSelected(null);
+ }
+
return found;
}
@@ -166,15 +203,17 @@ public class SdkTargetSelector {
private void adjustColumnsWidth(final Table table,
final TableColumn column0,
final TableColumn column1,
- final TableColumn column2) {
+ final TableColumn column2,
+ final TableColumn column3) {
// Add a listener to resize the column to the full width of the table
table.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
Rectangle r = table.getClientArea();
- column0.setWidth(r.width * 3 / 10); // 30%
- column1.setWidth(r.width * 5 / 10); // 50%
- column2.setWidth(r.width * 2 / 10); // 20%
+ column0.setWidth(r.width * 30 / 100); // 30%
+ column1.setWidth(r.width * 45 / 100); // 45%
+ column2.setWidth(r.width * 15 / 100); // 15%
+ column3.setWidth(r.width * 10 / 100); // 10%
}
});
}
@@ -238,6 +277,7 @@ public class SdkTargetSelector {
* <li>column 0: sdk name
* <li>column 1: sdk vendor
* <li>column 2: sdk api name
+ * <li>column 3: sdk version
* </ul>
*/
private void fillTable(final Table table) {
@@ -249,6 +289,7 @@ public class SdkTargetSelector {
item.setText(0, target.getName());
item.setText(1, target.getVendor());
item.setText(2, target.getApiVersionName());
+ item.setText(3, Integer.toString(target.getApiVersionNumber()));
}
} else {
table.setEnabled(false);
@@ -257,6 +298,7 @@ public class SdkTargetSelector {
item.setText(0, "--");
item.setText(1, "No target available");
item.setText(2, "--");
+ item.setText(3, "--");
}
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/VmSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/VmSelector.java
new file mode 100644
index 0000000..dcc0b9e
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/VmSelector.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 com.android.sdkuilib;
+
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.vm.VmManager.VmInfo;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+
+import java.util.ArrayList;
+
+
+/**
+ * The VM selector is a table that is added to the given parent composite.
+ * <p/>
+ * To use, create it using {@link #VmSelector(Composite, VmInfo[], boolean)} then
+ * call {@link #setSelection(VmInfo)}, {@link #setSelectionListener(SelectionListener)}
+ * and finally use {@link #getFirstSelected()} or {@link #getAllSelected()} to retrieve the
+ * selection.
+ */
+public final class VmSelector {
+
+ private VmInfo[] mVms;
+ private final boolean mAllowMultipleSelection;
+ private SelectionListener mSelectionListener;
+ private Table mTable;
+ private Label mDescription;
+
+ /**
+ * Creates a new SDK Target Selector.
+ *
+ * @param parent The parent composite where the selector will be added.
+ * @param vms The list of vms. This is <em>not</em> copied, the caller must not modify.
+ * @param allowMultipleSelection True if more than one SDK target can be selected at the same
+ * time.
+ */
+ public VmSelector(Composite parent, VmInfo[] vms, boolean allowMultipleSelection) {
+ mVms = vms;
+
+ // Layout has 1 column
+ Composite group = new Composite(parent, SWT.NONE);
+ group.setLayout(new GridLayout());
+ group.setLayoutData(new GridData(GridData.FILL_BOTH));
+ group.setFont(parent.getFont());
+
+ mAllowMultipleSelection = allowMultipleSelection;
+ mTable = new Table(group, SWT.CHECK | SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER);
+ mTable.setHeaderVisible(true);
+ mTable.setLinesVisible(false);
+
+ GridData data = new GridData();
+ data.grabExcessVerticalSpace = true;
+ data.grabExcessHorizontalSpace = true;
+ data.horizontalAlignment = GridData.FILL;
+ data.verticalAlignment = GridData.FILL;
+ mTable.setLayoutData(data);
+
+ mDescription = new Label(group, SWT.WRAP);
+ mDescription.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ // create the table columns
+ final TableColumn column0 = new TableColumn(mTable, SWT.NONE);
+ column0.setText("VM Name");
+ final TableColumn column1 = new TableColumn(mTable, SWT.NONE);
+ column1.setText("Target Name");
+ final TableColumn column2 = new TableColumn(mTable, SWT.NONE);
+ column2.setText("API Level");
+ final TableColumn column3 = new TableColumn(mTable, SWT.NONE);
+ column3.setText("SDK");
+
+ adjustColumnsWidth(mTable, column0, column1, column2, column3);
+ setupSelectionListener(mTable);
+ fillTable(mTable, null /* target filter */);
+ setupTooltip(mTable);
+ }
+
+ /**
+ * Sets a new set of VM, with an optional filter.
+ * <p/>This must be called from the UI thread.
+ *
+ * @param vms The list of vms. This is <em>not</em> copied, the caller must not modify.
+ * @param filter An IAndroidTarget. If non-null, only VM whose target are compatible with the
+ * filter target will displayed an available for selection.
+ */
+ public void setVms(VmInfo[] vms, IAndroidTarget filter) {
+ mVms = vms;
+ fillTable(mTable, filter);
+ }
+
+ /**
+ * Returns the list of known Vms.
+ * <p/>
+ * This is not a copy. Callers must <em>not</em> modify this array.
+ */
+ public VmInfo[] getVms() {
+ return mVms;
+ }
+
+ /**
+ * Sets a selection listener. Set it to null to remove it.
+ * The listener will be called <em>after</em> this table processed its selection
+ * events so that the caller can see the updated state.
+ * <p/>
+ * The event's item contains a {@link TableItem}.
+ * The {@link TableItem#getData()} contains an {@link IAndroidTarget}.
+ * <p/>
+ * It is recommended that the caller uses the {@link #getFirstSelected()} and
+ * {@link #getAllSelected()} methods instead.
+ *
+ * @param selectionListener The new listener or null to remove it.
+ */
+ public void setSelectionListener(SelectionListener selectionListener) {
+ mSelectionListener = selectionListener;
+ }
+
+ /**
+ * Sets the current target selection.
+ * <p/>
+ * If the selection is actually changed, this will invoke the selection listener
+ * (if any) with a null event.
+ *
+ * @param target the target to be selection
+ * @return true if the target could be selected, false otherwise.
+ */
+ public boolean setSelection(VmInfo target) {
+ boolean found = false;
+ boolean modified = false;
+ for (TableItem i : mTable.getItems()) {
+ if ((VmInfo) i.getData() == target) {
+ found = true;
+ if (!i.getChecked()) {
+ modified = true;
+ i.setChecked(true);
+ }
+ } else if (i.getChecked()) {
+ modified = true;
+ i.setChecked(false);
+ }
+ }
+
+ if (modified && mSelectionListener != null) {
+ mSelectionListener.widgetSelected(null);
+ }
+
+ return found;
+ }
+
+ /**
+ * Returns all selected items.
+ * This is useful when the table is in multiple-selection mode.
+ *
+ * @see #getFirstSelected()
+ * @return An array of selected items. The list can be empty but not null.
+ */
+ public VmInfo[] getAllSelected() {
+ ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
+ for (TableItem i : mTable.getItems()) {
+ if (i.getChecked()) {
+ list.add((IAndroidTarget) i.getData());
+ }
+ }
+ return list.toArray(new VmInfo[list.size()]);
+ }
+
+ /**
+ * Returns the first selected item.
+ * This is useful when the table is in single-selection mode.
+ *
+ * @see #getAllSelected()
+ * @return The first selected item or null.
+ */
+ public VmInfo getFirstSelected() {
+ for (TableItem i : mTable.getItems()) {
+ if (i.getChecked()) {
+ return (VmInfo) i.getData();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds a listener to adjust the columns width when the parent is resized.
+ * <p/>
+ * If we need something more fancy, we might want to use this:
+ * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co
+ */
+ private void adjustColumnsWidth(final Table table,
+ final TableColumn column0,
+ final TableColumn column1,
+ final TableColumn column2,
+ final TableColumn column3) {
+ // Add a listener to resize the column to the full width of the table
+ table.addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ Rectangle r = table.getClientArea();
+ column0.setWidth(r.width * 30 / 100); // 30%
+ column1.setWidth(r.width * 45 / 100); // 45%
+ column2.setWidth(r.width * 15 / 100); // 15%
+ column3.setWidth(r.width * 10 / 100); // 10%
+ }
+ });
+ }
+
+
+ /**
+ * Creates a selection listener that will check or uncheck the whole line when
+ * double-clicked (aka "the default selection").
+ */
+ private void setupSelectionListener(final Table table) {
+ // Add a selection listener that will check/uncheck items when they are double-clicked
+ table.addSelectionListener(new SelectionListener() {
+ /** Default selection means double-click on "most" platforms */
+ public void widgetDefaultSelected(SelectionEvent e) {
+ if (e.item instanceof TableItem) {
+ TableItem i = (TableItem) e.item;
+ i.setChecked(!i.getChecked());
+ enforceSingleSelection(i);
+ updateDescription(i);
+ }
+
+ if (mSelectionListener != null) {
+ mSelectionListener.widgetDefaultSelected(e);
+ }
+ }
+
+ public void widgetSelected(SelectionEvent e) {
+ if (e.item instanceof TableItem) {
+ TableItem i = (TableItem) e.item;
+ enforceSingleSelection(i);
+ updateDescription(i);
+ }
+
+ if (mSelectionListener != null) {
+ mSelectionListener.widgetSelected(e);
+ }
+ }
+
+ /**
+ * If we're not in multiple selection mode, uncheck all other
+ * items when this one is selected.
+ */
+ private void enforceSingleSelection(TableItem item) {
+ if (!mAllowMultipleSelection && item.getChecked()) {
+ Table parentTable = item.getParent();
+ for (TableItem i2 : parentTable.getItems()) {
+ if (i2 != item && i2.getChecked()) {
+ i2.setChecked(false);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Fills the table with all VM.
+ * The table columns are:
+ * <ul>
+ * <li>column 0: sdk name
+ * <li>column 1: sdk vendor
+ * <li>column 2: sdk api name
+ * <li>column 3: sdk version
+ * </ul>
+ */
+ private void fillTable(final Table table, IAndroidTarget filter) {
+ table.removeAll();
+ if (mVms != null && mVms.length > 0) {
+ table.setEnabled(true);
+ for (VmInfo vm : mVms) {
+ if (filter == null || filter.isCompatibleBaseFor(vm.getTarget())) {
+ TableItem item = new TableItem(table, SWT.NONE);
+ item.setData(vm);
+ item.setText(0, vm.getName());
+ IAndroidTarget target = vm.getTarget();
+ item.setText(1, target.getFullName());
+ item.setText(2, target.getApiVersionName());
+ item.setText(3, Integer.toString(target.getApiVersionNumber()));
+ }
+ }
+ }
+
+ if (table.getItemCount() == 0) {
+ table.setEnabled(false);
+ TableItem item = new TableItem(table, SWT.NONE);
+ item.setData(null);
+ item.setText(0, "--");
+ item.setText(1, "No VM available");
+ item.setText(2, "--");
+ item.setText(3, "--");
+ }
+ }
+
+ /**
+ * Sets up a tooltip that displays the current item description.
+ * <p/>
+ * Displaying a tooltip over the table looks kind of odd here. Instead we actually
+ * display the description in a label under the table.
+ */
+ private void setupTooltip(final Table table) {
+ /*
+ * Reference:
+ * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup
+ */
+
+ final Listener listener = new Listener() {
+ public void handleEvent(Event event) {
+
+ switch(event.type) {
+ case SWT.KeyDown:
+ case SWT.MouseExit:
+ case SWT.MouseDown:
+ return;
+
+ case SWT.MouseHover:
+ updateDescription(table.getItem(new Point(event.x, event.y)));
+ break;
+
+ case SWT.Selection:
+ if (event.item instanceof TableItem) {
+ updateDescription((TableItem) event.item);
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ }
+ };
+
+ table.addListener(SWT.Dispose, listener);
+ table.addListener(SWT.KeyDown, listener);
+ table.addListener(SWT.MouseMove, listener);
+ table.addListener(SWT.MouseHover, listener);
+ }
+
+ /**
+ * Updates the description label with the path of the item's VM, if any.
+ */
+ private void updateDescription(TableItem item) {
+ if (item != null) {
+ Object data = item.getData();
+ if (data instanceof VmInfo) {
+ String newTooltip = ((VmInfo) data).getPath();
+ mDescription.setText(newTooltip == null ? "" : newTooltip); //$NON-NLS-1$
+ }
+ }
+ }
+}