diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-01-09 17:51:19 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-01-09 17:51:19 -0800 |
commit | a1514dae8668c48feae5436285e3db0ba2133ec3 (patch) | |
tree | 115c685f5c4c810cd317e8a6903e8d2d96749051 /sdkmanager | |
parent | 9ca1c0b33cc3fc28c21a915730797ec01e71a152 (diff) | |
download | sdk-a1514dae8668c48feae5436285e3db0ba2133ec3.zip sdk-a1514dae8668c48feae5436285e3db0ba2133ec3.tar.gz sdk-a1514dae8668c48feae5436285e3db0ba2133ec3.tar.bz2 |
auto import from //branches/cupcake/...@125939
Diffstat (limited to 'sdkmanager')
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$ + } + } + } +} |