From 9e4f312fe9c0820638503b40b5fcbb6571e23354 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Fri, 27 Jul 2007 17:29:33 +0000
Subject: [PATCH] Partial 95% fix for issue 1831 - dsconfig interactive mode.

---
 opends/src/server/org/opends/server/messages/ToolMessages.java                   |  101 +++++++
 opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java |   35 ++
 opends/src/server/org/opends/server/tools/dsconfig/PropertyValueReader.java      |  623 ++++++++++++++++++++++++++++++++++++++++++++
 opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java  |   34 ++
 4 files changed, 789 insertions(+), 4 deletions(-)

diff --git a/opends/src/server/org/opends/server/messages/ToolMessages.java b/opends/src/server/org/opends/server/messages/ToolMessages.java
index aa7f8c1..eb08e12 100644
--- a/opends/src/server/org/opends/server/messages/ToolMessages.java
+++ b/opends/src/server/org/opends/server/messages/ToolMessages.java
@@ -9379,11 +9379,88 @@
        CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1239;
 
   /**
+   * The message ID for the message that will be used in the property
+   * editing menu for resetting.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_MENU_RESET =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1240;
+
+  /**
+   * The message ID for the message that will be used in the property
+   * editing menu for setting a value.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_MENU_SET =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1241;
+
+  /**
+   * The message ID for the message that will be used in the property
+   * editing menu for adding a value.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_MENU_ADD =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1242;
+
+  /**
+   * The message ID for the message that will be used in the property
+   * editing menu for removing a value.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_MENU_REMOVE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1243;
+
+  /**
+   * The message ID for the message that will be used in the property
+   * editing menu for the continue option.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_MENU_CONTINUE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1244;
+
+  /**
+   * The message ID for the message that will be used in the property
+   * editor as the prompt for removing a value.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_PROMPT_REMOVE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1245;
+
+  /**
+   * The message ID for the message that will be used in the property
+   * editor as the prompt for selecting a value.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_PROMPT_SELECT_VALUE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1246;
+
+  /**
+   * The message ID for the message that will be used in the property
+   * editor as the prompt for entering a value.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_PROMPT_ENTER_VALUE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1247;
+
+  /**
+   * The message ID for the message that will be used in the property
+   * editor as the title prompt for the property selection menu.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_MENU_TITLE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1248;
+
+  /**
+   * The message ID for the message that will be used in the property
+   * editor as the prompt for mandatory properties.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_PROMPT_MANDATORY =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1249;
+
+  /**
+   * The message ID for the message that will be used in the property
+   * editor as the prompt for property modification menu.
+   */
+  public static final int MSGID_DSCFG_VALUE_READER_PROMPT_MODIFY_MENU =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1250;
+
+  /**
    * The message ID for the message that will be used as the description of the
    * clearBackend argument.  This does not take any arguments.
    */
   public static final int MSGID_LDIFIMPORT_DESCRIPTION_CLEAR_BACKEND =
-       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1240;
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1251;
 
     /**
    * The message ID for the message that will be used if neither the
@@ -9392,7 +9469,7 @@
    * includeBranchStrings and backendID options.
    */
   public static final int MSGID_LDIFIMPORT_MISSING_BACKEND_ARGUMENT =
-      CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1241;
+      CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1252;
 
 
   /**
@@ -9402,7 +9479,7 @@
    * option as an argument.
    */
   public static final int MSGID_LDIFIMPORT_MISSING_CLEAR_BACKEND =
-      CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1242;
+      CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1253;
 
   /**
    * Associates a set of generic messages with the message IDs defined in this
@@ -12432,6 +12509,24 @@
                     "Invalid response. Please enter a value between 1 and %s");
     registerMessage(MSGID_DSCFG_ERROR_GENERAL_CONFIRM,
                     "Invalid response. Please enter \"%s\" or \"%s\"");
+    registerMessage(MSGID_DSCFG_VALUE_READER_MENU_ADD, "add a value");
+    registerMessage(MSGID_DSCFG_VALUE_READER_MENU_REMOVE, "remove a value");
+    registerMessage(MSGID_DSCFG_VALUE_READER_MENU_SET, "modify the value");
+    registerMessage(MSGID_DSCFG_VALUE_READER_MENU_RESET,
+        "reset the value back to its default");
+    registerMessage(MSGID_DSCFG_VALUE_READER_MENU_CONTINUE, "continue");
+    registerMessage(MSGID_DSCFG_VALUE_READER_PROMPT_REMOVE,
+        "Select the value to be removed from the \"%s\" property:");
+    registerMessage(MSGID_DSCFG_VALUE_READER_PROMPT_SELECT_VALUE,
+        "Select a value for the \"%s\" property:");
+    registerMessage(MSGID_DSCFG_VALUE_READER_PROMPT_ENTER_VALUE,
+        "Enter a value for the \"%s\" property:");
+    registerMessage(MSGID_DSCFG_VALUE_READER_MENU_TITLE,
+        "Select a property to be edited, or enter \"%d\" to continue:");
+    registerMessage(MSGID_DSCFG_VALUE_READER_PROMPT_MANDATORY,
+        "The property \"%s\" is mandatory and must have a value specified");
+    registerMessage(MSGID_DSCFG_VALUE_READER_PROMPT_MODIFY_MENU,
+        "Do you want to modify the \"%s\" property?");
     registerMessage(MSGID_LDIFIMPORT_DESCRIPTION_CLEAR_BACKEND,
                     "Remove all entries for all base DNs in the backend " +
                     "before importing");
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java b/opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java
index da46c18..489d4fc 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java
@@ -40,7 +40,9 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedMap;
+import java.util.SortedSet;
 import java.util.TreeMap;
+import java.util.TreeSet;
 
 import org.opends.server.admin.AbstractManagedObjectDefinition;
 import org.opends.server.admin.Configuration;
@@ -407,6 +409,10 @@
     // Create the naming arguments.
     this.namingArgs = createNamingArgs(subCommand, c, true);
 
+    // Register common arguments.
+    registerAdvancedModeArgument(this.subCommand,
+        MSGID_DSCFG_DESCRIPTION_ADVANCED_SET, r.getUserFriendlyName());
+
     // Create the --property argument which is used to specify
     // property values.
     this.propertySetArgument = new StringArgument(OPTION_DSCFG_LONG_SET,
@@ -588,6 +594,32 @@
         setProperty(child, provider, pd);
       }
 
+      // Interactively set properties if applicable.
+      if (getConsoleApplication().isInteractive()) {
+        SortedSet<PropertyDefinition<?>> properties =
+          new TreeSet<PropertyDefinition<?>>();
+
+        for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) {
+          if (pd.hasOption(PropertyOption.HIDDEN)) {
+            continue;
+          }
+
+          if (pd.hasOption(PropertyOption.MONITORING)) {
+            continue;
+          }
+
+          if (!isAdvancedMode() && pd.hasOption(PropertyOption.ADVANCED)) {
+            continue;
+          }
+
+          properties.add(pd);
+        }
+
+        PropertyValueReader reader =
+          new PropertyValueReader(getConsoleApplication());
+        reader.readAll(child, properties);
+      }
+
       // Confirm commit.
       String prompt = String.format(Messages.getString("create.confirm"), d
           .getUserFriendlyName());
@@ -649,7 +681,7 @@
       final List<DefaultBehaviorException> exceptions)
       throws ArgumentException, ClientException {
     int msgID = MSGID_DSCFG_CREATE_NAME_PROMPT;
-    String msg = getMessage(msgID, relation.getUserFriendlyName());
+    String msg = getMessage(msgID, d.getUserFriendlyName());
     ValidationCallback<ManagedObject<? extends C>> validator =
       new ValidationCallback<ManagedObject<? extends C>>() {
 
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/PropertyValueReader.java b/opends/src/server/org/opends/server/tools/dsconfig/PropertyValueReader.java
new file mode 100644
index 0000000..7881af0
--- /dev/null
+++ b/opends/src/server/org/opends/server/tools/dsconfig/PropertyValueReader.java
@@ -0,0 +1,623 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.tools.dsconfig;
+
+
+
+import static org.opends.server.messages.MessageHandler.*;
+import static org.opends.server.messages.ToolMessages.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
+import org.opends.server.admin.AliasDefaultBehaviorProvider;
+import org.opends.server.admin.BooleanPropertyDefinition;
+import org.opends.server.admin.DefaultBehaviorProviderVisitor;
+import org.opends.server.admin.DefinedDefaultBehaviorProvider;
+import org.opends.server.admin.EnumPropertyDefinition;
+import org.opends.server.admin.IllegalPropertyValueStringException;
+import org.opends.server.admin.PropertyDefinition;
+import org.opends.server.admin.PropertyDefinitionVisitor;
+import org.opends.server.admin.PropertyOption;
+import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider;
+import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
+import org.opends.server.admin.UnknownPropertyDefinitionException;
+import org.opends.server.admin.client.ManagedObject;
+import org.opends.server.tools.ClientException;
+import org.opends.server.util.args.ArgumentException;
+import org.opends.server.util.table.TableBuilder;
+import org.opends.server.util.table.TextTablePrinter;
+
+
+
+/**
+ * A class responsible for interactively retrieving property values
+ * from the console.
+ */
+final class PropertyValueReader {
+
+  /**
+   * A help call-back which displays help on a property.
+   */
+  private static final class PropertyHelpCallback implements HelpCallback {
+
+    // The managed object.
+    private final ManagedObject<?> mo;
+
+    // The property definition.
+    private final PropertyDefinition<?> pd;
+
+
+
+    /**
+     * Creates a new property help call-back.
+     *
+     * @param mo
+     *          The managed object.
+     * @param pd
+     *          The property definition.
+     */
+    public PropertyHelpCallback(ManagedObject<?> mo, PropertyDefinition<?> pd) {
+      this.mo = mo;
+      this.pd = pd;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void display(ConsoleApplication app) {
+      HelpSubCommandHandler help = HelpSubCommandHandler.getInstance();
+      app.println();
+      help.displayVerboseSingleProperty(mo.getManagedObjectDefinition(), pd
+          .getName(), app.getErrorStream());
+    }
+
+  }
+
+
+
+  /**
+   * A menu call-back used for editing property values.
+   */
+  private interface MenuCallback {
+
+    /**
+     * Invoke the menu call-back.
+     *
+     * @param mo
+     *          The managed object.
+     * @param pd
+     *          The property definition to be modified.
+     * @param <T>
+     *          The type of property to be edited.
+     * @throws ArgumentException
+     *           If the user input could not be retrieved for some
+     *           reason.
+     */
+    <T> void invoke(ManagedObject<?> mo, PropertyDefinition<T> pd)
+        throws ArgumentException;
+  }
+
+
+
+  /**
+   * A menu call-back for adding values to a property.
+   */
+  private final class AddValueMenuCallback implements MenuCallback {
+
+    /**
+     * {@inheritDoc}
+     */
+    public <T> void invoke(ManagedObject<?> mo, PropertyDefinition<T> pd)
+        throws ArgumentException {
+      // TODO: display error if the value already exists.
+
+      // TODO: for enumerations, only display the values which are not
+      // already assigned.
+      T value = read(mo, pd);
+      SortedSet<T> values = mo.getPropertyValues(pd);
+      values.add(value);
+      mo.setPropertyValues(pd, values);
+    }
+
+  }
+
+
+
+  /**
+   * A menu call-back for removing values from a property.
+   */
+  private final class RemoveValueMenuCallback implements MenuCallback {
+
+    /**
+     * {@inheritDoc}
+     */
+    public <T> void invoke(ManagedObject<?> mo, PropertyDefinition<T> pd)
+        throws ArgumentException {
+      PropertyValuePrinter printer =
+        new PropertyValuePrinter(null, null, false);
+      SortedSet<T> values = mo.getPropertyValues(pd);
+
+      List<String> descriptions = new ArrayList<String>(values.size());
+      List<T> lvalues = new ArrayList<T>(values.size());
+
+      for (T value : values) {
+        descriptions.add(printer.print(pd, value));
+        lvalues.add(value);
+      }
+
+      String promptMsg =
+        getMessage(MSGID_DSCFG_VALUE_READER_PROMPT_REMOVE, pd.getName());
+      T value = app.readChoice(promptMsg, descriptions, lvalues, null);
+      values.remove(value);
+      mo.setPropertyValues(pd, values);
+    }
+
+  }
+
+
+
+  /**
+   * A menu call-back for resetting a property back to its defaults.
+   */
+  private final class ResetValueMenuCallback implements MenuCallback {
+
+    /**
+     * {@inheritDoc}
+     */
+    public <T> void invoke(ManagedObject<?> mo, PropertyDefinition<T> pd)
+        throws ArgumentException {
+      mo.setPropertyValue(pd, null);
+    }
+
+  }
+
+
+
+  /**
+   * A menu call-back for setting a single-valued property.
+   */
+  private final class SetValueMenuCallback implements MenuCallback {
+
+    /**
+     * {@inheritDoc}
+     */
+    public <T> void invoke(ManagedObject<?> mo, PropertyDefinition<T> pd)
+        throws ArgumentException {
+      T value = read(mo, pd);
+      mo.setPropertyValue(pd, value);
+    }
+
+  }
+
+
+
+  /**
+   * The reader implementation.
+   */
+  private final class Visitor extends PropertyDefinitionVisitor<String, Void> {
+
+    // Any argument exception that was caught during processing.
+    private ArgumentException ae = null;
+
+
+
+    /**
+     * Read a value from the console for the provided property
+     * definition.
+     *
+     * @param pd
+     *          The property definition.
+     * @return Returns the string value.
+     * @throws ArgumentException
+     *           If the user input could not be retrieved for some
+     *           reason.
+     */
+    public String read(PropertyDefinition<?> pd) throws ArgumentException {
+      String result = pd.accept(this, null);
+
+      if (result != null) {
+        return result;
+      } else if (ae != null) {
+        throw ae;
+      } else {
+        throw new IllegalStateException(
+            "No result and no ArgumentException caught");
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String visitBoolean(BooleanPropertyDefinition d, Void p) {
+      List<String> values = Arrays.asList(new String[] {
+          "false", "true"
+      });
+      try {
+        String promptMsg = getMessage(
+            MSGID_DSCFG_VALUE_READER_PROMPT_SELECT_VALUE, d.getName());
+        return app.readChoice(promptMsg, values, values, null);
+      } catch (ArgumentException e) {
+        ae = e;
+        return null;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <E extends Enum<E>> String visitEnum(EnumPropertyDefinition<E> d,
+        Void p) {
+      SortedMap<String, String> map = new TreeMap<String, String>();
+      for (E value : EnumSet.allOf(d.getEnumClass())) {
+        String s = String.format("%s : %s", value.toString(), d
+            .getValueSynopsis(value));
+        map.put(value.toString(), s);
+      }
+
+      List<String> descriptions = new ArrayList<String>(map.values());
+      List<String> values = new ArrayList<String>(map.keySet());
+      try {
+        String promptMsg = getMessage(
+            MSGID_DSCFG_VALUE_READER_PROMPT_SELECT_VALUE, d.getName());
+        return app.readChoice(promptMsg, descriptions, values, null);
+      } catch (ArgumentException e) {
+        ae = e;
+        return null;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String visitUnknown(PropertyDefinition<?> d, Void p)
+        throws UnknownPropertyDefinitionException {
+      try {
+        String promptMsg = getMessage(
+            MSGID_DSCFG_VALUE_READER_PROMPT_ENTER_VALUE, d.getName());
+        return app.readLineOfInput(promptMsg);
+      } catch (ArgumentException e) {
+        ae = e;
+        return null;
+      }
+    }
+
+  }
+
+  // The application console.
+  private final ConsoleApplication app;
+
+
+
+  /**
+   * Create a new property value reader which will read from the
+   * provider application console.
+   *
+   * @param app
+   *          The application console.
+   */
+  public PropertyValueReader(ConsoleApplication app) {
+    this.app = app;
+  }
+
+
+
+  /**
+   * Asks the user to input a single value for the provided property
+   * definition. The value will be validated according to the
+   * constraints of the property definition and its decoded value
+   * returned.
+   *
+   * @param <T>
+   *          The underlying type of the property definition.
+   * @param mo
+   *          The managed object.
+   * @param pd
+   *          The property definition.
+   * @return Returns the validated string value.
+   * @throws ArgumentException
+   *           If the user input could not be retrieved for some
+   *           reason.
+   */
+  public <T> T read(ManagedObject<?> mo, PropertyDefinition<T> pd)
+      throws ArgumentException {
+    while (true) {
+      app.println();
+      String value = pd.accept(new Visitor(), null);
+
+      try {
+        return pd.decodeValue(value);
+      } catch (IllegalPropertyValueStringException e) {
+        app.println();
+        app.printMessage(ArgumentExceptionFactory.adaptPropertyException(e,
+            mo.getManagedObjectDefinition()).getMessage());
+      }
+    }
+  }
+
+
+
+  /**
+   * Edit the properties of a managed object. Only the properties
+   * listed in the provided collection will be accessible to the
+   * client. It is up to the caller to ensure that the list of
+   * properties does not include read-only, monitoring, hidden, or
+   * advanced properties as appropriate.
+   *
+   * @param mo
+   *          The managed object.
+   * @param c
+   *          The collection of properties which can be edited.
+   * @throws ArgumentException
+   *           If the user input could not be retrieved for some
+   *           reason.
+   */
+  public void readAll(ManagedObject<?> mo, Collection<PropertyDefinition<?>> c)
+      throws ArgumentException {
+    // Get values for this missing mandatory property.
+    for (PropertyDefinition<?> pd : c) {
+      if (pd.hasOption(PropertyOption.MANDATORY)) {
+        if (mo.getPropertyValues(pd).isEmpty()) {
+          editProperty(mo, pd);
+        }
+      }
+    }
+
+    // Now let users modify remaining properties.
+    boolean isFinished = false;
+    PropertyValuePrinter valuePrinter = new PropertyValuePrinter(null, null,
+        false);
+    while (!isFinished) {
+      // Display a menu allowing users to edit individual options.
+      TableBuilder builder = new TableBuilder();
+      builder.appendHeading();
+      builder.appendHeading(getMessage(MSGID_DSCFG_HEADING_PROPERTY_NAME));
+      builder.appendHeading(getMessage(MSGID_DSCFG_HEADING_PROPERTY_VALUE));
+
+      int i = 0;
+      List<PropertyDefinition<?>> pl = new ArrayList<PropertyDefinition<?>>(c);
+      for (PropertyDefinition<?> pd : pl) {
+        builder.startRow();
+        builder.appendCell("[" + i + "]");
+        builder.appendCell(pd.getName());
+
+        String values = getPropertyValuesAsString(mo, pd, valuePrinter);
+        builder.appendCell(values);
+        i++;
+      }
+
+      builder.startRow();
+      builder.startRow();
+      builder.appendCell("[" + i + "]");
+      builder.appendCell(getMessage(MSGID_DSCFG_VALUE_READER_MENU_CONTINUE));
+
+      // Display the menu.
+      app.println();
+      app.printMessage(getMessage(MSGID_DSCFG_VALUE_READER_MENU_TITLE, i));
+      app.println();
+
+      TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+      printer.setColumnWidth(2, 0);
+      builder.print(printer);
+
+      // Get the user input.
+      final int size = i;
+      String promptMsg =
+        getMessage(MSGID_DSCFG_GENERAL_CHOICE_PROMPT_NOHELP, i);
+      ValidationCallback<Integer> validator =
+        new ValidationCallback<Integer>() {
+
+        public Integer validate(ConsoleApplication app, String input) {
+          String ninput = input.trim();
+
+          try {
+            int j = Integer.parseInt(ninput);
+            if (j < 1 || j > size) {
+              throw new NumberFormatException();
+            }
+            return j;
+          } catch (NumberFormatException e) {
+            app.println();
+            String errMsg = getMessage(MSGID_DSCFG_ERROR_GENERAL_CHOICE, size);
+            app.printMessage(errMsg);
+            return null;
+          }
+        }
+      };
+
+      // Get the choice.
+      int choice;
+      try {
+        choice = app.readValidatedInput(promptMsg, validator);
+      } catch (ClientException e) {
+        // Should never happen.
+        throw new RuntimeException(e);
+      }
+
+      if (choice == size) {
+        isFinished = true;
+      } else {
+        editProperty(mo, pl.get(choice));
+      }
+    }
+  }
+
+
+
+  // Interactively edit a property.
+  private <T> void editProperty(ManagedObject<?> mo, PropertyDefinition<T> pd)
+      throws ArgumentException {
+    // If the property is mandatory then make sure we prompt for an
+    // initial value.
+    if (pd.hasOption(PropertyOption.MANDATORY)) {
+      if (mo.getPropertyValues(pd).isEmpty()) {
+        app.println();
+        String promptMsg = getMessage(
+            MSGID_DSCFG_VALUE_READER_PROMPT_MANDATORY, pd.getName());
+        app.printMessage(promptMsg);
+        T value = read(mo, pd);
+        mo.setPropertyValue(pd, value);
+      }
+    }
+
+    boolean isFinished = false;
+    while (!isFinished) {
+      // Construct a list of menu options and their call-backs.
+      List<String> descriptions = new ArrayList<String>();
+      List<MenuCallback> callbacks = new ArrayList<MenuCallback>();
+
+      if (pd.hasOption(PropertyOption.MULTI_VALUED)) {
+        descriptions.add(getMessage(MSGID_DSCFG_VALUE_READER_MENU_ADD));
+        callbacks.add(new AddValueMenuCallback());
+
+        if (!mo.getPropertyValues(pd).isEmpty()) {
+          descriptions.add(getMessage(MSGID_DSCFG_VALUE_READER_MENU_REMOVE));
+          callbacks.add(new RemoveValueMenuCallback());
+        }
+      } else {
+        descriptions.add(getMessage(MSGID_DSCFG_VALUE_READER_MENU_SET));
+        callbacks.add(new SetValueMenuCallback());
+      }
+
+      if (!pd.hasOption(PropertyOption.MANDATORY)
+          || !(pd.getDefaultBehaviorProvider()
+              instanceof UndefinedDefaultBehaviorProvider)) {
+        descriptions.add(getMessage(MSGID_DSCFG_VALUE_READER_MENU_RESET));
+        callbacks.add(new ResetValueMenuCallback());
+      }
+
+      descriptions.add(getMessage(MSGID_DSCFG_VALUE_READER_MENU_CONTINUE));
+      callbacks.add(null);
+
+      // FIXME: display current values of the property.
+      String promptMsg = getMessage(
+          MSGID_DSCFG_VALUE_READER_PROMPT_MODIFY_MENU, pd.getName());
+      MenuCallback callback = app.readChoice(promptMsg, descriptions,
+          callbacks, new PropertyHelpCallback(mo, pd));
+
+      if (callback != null) {
+        callback.invoke(mo, pd);
+      } else {
+        isFinished = true;
+      }
+    }
+  }
+
+
+
+  // Display the set of values associated with a property.
+  private <T> String getPropertyValuesAsString(ManagedObject<?> mo,
+      PropertyDefinition<T> pd, PropertyValuePrinter valuePrinter) {
+    SortedSet<T> values = mo.getPropertyValues(pd);
+    if (values.isEmpty()) {
+      // There are no values or default values. Display the default
+      // behavior for alias values.
+      DefaultBehaviorProviderVisitor<T, String, Void> visitor =
+        new DefaultBehaviorProviderVisitor<T, String, Void>() {
+
+        public String visitAbsoluteInherited(
+            AbsoluteInheritedDefaultBehaviorProvider<T> d, Void p) {
+          // Should not happen - inherited default values are
+          // displayed as normal values.
+          throw new IllegalStateException();
+        }
+
+
+
+        public String visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
+          if (app.isVerbose()) {
+            return d.getSynopsis();
+          } else {
+            return null;
+          }
+        }
+
+
+
+        public String visitDefined(
+            DefinedDefaultBehaviorProvider<T> d, Void p) {
+          // Should not happen - real default values are displayed as
+          // normal values.
+          throw new IllegalStateException();
+        }
+
+
+
+        public String visitRelativeInherited(
+            RelativeInheritedDefaultBehaviorProvider<T> d, Void p) {
+          // Should not happen - inherited default values are
+          // displayed as normal values.
+          throw new IllegalStateException();
+        }
+
+
+
+        public String visitUndefined(UndefinedDefaultBehaviorProvider<T> d,
+            Void p) {
+          return null;
+        }
+      };
+
+      String content = pd.getDefaultBehaviorProvider().accept(visitor, null);
+      if (content == null) {
+        return "-";
+      } else {
+        return content;
+      }
+    } else {
+      StringBuilder sb = new StringBuilder();
+      boolean isFirst = true;
+      for (T value : values) {
+        if (!isFirst) {
+          sb.append(", ");
+        }
+        sb.append(valuePrinter.print(pd, value));
+        isFirst = false;
+      }
+
+      return sb.toString();
+    }
+  }
+}
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java b/opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java
index 7a99ff4..46ceafa 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java
@@ -35,6 +35,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.SortedSet;
 import java.util.TreeSet;
 
 import org.opends.server.admin.DefinitionDecodingException;
@@ -250,6 +251,10 @@
     // Create the naming arguments.
     this.namingArgs = createNamingArgs(subCommand, path, false);
 
+    // Register common arguments.
+    registerAdvancedModeArgument(this.subCommand,
+        MSGID_DSCFG_DESCRIPTION_ADVANCED_SET, r.getUserFriendlyName());
+
     // Create the --set argument.
     this.propertySetArgument = new StringArgument(OPTION_DSCFG_LONG_SET,
         OPTION_DSCFG_SHORT_SET, OPTION_DSCFG_LONG_SET, false, true, true,
@@ -494,6 +499,36 @@
       }
     }
 
+    // Interactively set properties if applicable.
+    if (getConsoleApplication().isInteractive()) {
+      SortedSet<PropertyDefinition<?>> properties =
+        new TreeSet<PropertyDefinition<?>>();
+
+      for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) {
+        if (pd.hasOption(PropertyOption.HIDDEN)) {
+          continue;
+        }
+
+        if (pd.hasOption(PropertyOption.READ_ONLY)) {
+          continue;
+        }
+
+        if (pd.hasOption(PropertyOption.MONITORING)) {
+          continue;
+        }
+
+        if (!isAdvancedMode() && pd.hasOption(PropertyOption.ADVANCED)) {
+          continue;
+        }
+
+        properties.add(pd);
+      }
+
+      PropertyValueReader reader =
+        new PropertyValueReader(getConsoleApplication());
+      reader.readAll(child, properties);
+    }
+
     try {
       // Confirm commit.
       String prompt = String.format(Messages.getString("modify.confirm"), d

--
Gitblit v1.10.0