/*
* 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
*
*
* Copyright 2008-2010 Sun Microsystems, Inc.
* Portions Copyright 2013 ForgeRock AS
*/
package org.opends.server.tools.dsconfig;
import static org.opends.messages.DSConfigMessages.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
import org.opends.server.admin.AbstractManagedObjectDefinition;
import org.opends.server.admin.AggregationPropertyDefinition;
import org.opends.server.admin.AliasDefaultBehaviorProvider;
import org.opends.server.admin.BooleanPropertyDefinition;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.ConfigurationClient;
import org.opends.server.admin.DefaultBehaviorProviderVisitor;
import org.opends.server.admin.DefinedDefaultBehaviorProvider;
import org.opends.server.admin.DefinitionDecodingException;
import org.opends.server.admin.EnumPropertyDefinition;
import org.opends.server.admin.IllegalPropertyValueException;
import org.opends.server.admin.IllegalPropertyValueStringException;
import org.opends.server.admin.InstantiableRelationDefinition;
import org.opends.server.admin.ManagedObjectDefinition;
import org.opends.server.admin.ManagedObjectNotFoundException;
import org.opends.server.admin.ManagedObjectPath;
import org.opends.server.admin.PropertyDefinition;
import org.opends.server.admin.PropertyDefinitionUsageBuilder;
import org.opends.server.admin.PropertyDefinitionVisitor;
import org.opends.server.admin.PropertyIsMandatoryException;
import org.opends.server.admin.PropertyIsReadOnlyException;
import org.opends.server.admin.PropertyIsSingleValuedException;
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.AuthorizationException;
import org.opends.server.admin.client.CommunicationException;
import org.opends.server.admin.client.ManagedObject;
import org.opends.server.admin.client.ManagedObjectDecodingException;
import org.opends.server.admin.client.ManagementContext;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.tools.ClientException;
import org.opends.server.util.Validator;
import org.opends.server.util.cli.CLIException;
import org.opends.server.util.cli.ConsoleApplication;
import org.opends.server.util.cli.HelpCallback;
import org.opends.server.util.cli.Menu;
import org.opends.server.util.cli.MenuBuilder;
import org.opends.server.util.cli.MenuCallback;
import org.opends.server.util.cli.MenuResult;
import org.opends.server.util.table.TableBuilder;
import org.opends.server.util.table.TextTablePrinter;
/**
* Common methods used for interactively editing properties.
*/
final class PropertyValueEditor {
/**
* A menu call-back which can be used to dynamically create new
* components when configuring aggregation based properties.
*/
private final class CreateComponentCallback
<C extends ConfigurationClient, S extends Configuration>
implements MenuCallback<String> {
// The aggregation property definition.
private final AggregationPropertyDefinition<C, S> pd;
// Creates a new component create call-back for the provided
// aggregation property definition.
private CreateComponentCallback(AggregationPropertyDefinition<C, S> pd) {
this.pd = pd;
}
/**
* {@inheritDoc}
*/
public MenuResult<String> invoke(ConsoleApplication app)
throws CLIException {
try {
// First get the parent managed object.
InstantiableRelationDefinition<?, ?> rd = pd.getRelationDefinition();
ManagedObjectPath<?, ?> path = pd.getParentPath();
Message ufn = rd.getUserFriendlyName();
ManagedObject<?> parent;
try {
parent = context.getManagedObject(path);
} catch (AuthorizationException e) {
Message msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(ufn);
throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,
msg);
} catch (DefinitionDecodingException e) {
Message pufn = path.getManagedObjectDefinition()
.getUserFriendlyName();
Message msg = ERR_DSCFG_ERROR_GET_PARENT_DDE.get(pufn, pufn, pufn);
throw new ClientException(LDAPResultCode.OTHER, msg);
} catch (ManagedObjectDecodingException e) {
Message pufn = path.getManagedObjectDefinition()
.getUserFriendlyName();
Message msg = ERR_DSCFG_ERROR_GET_PARENT_MODE.get(pufn);
throw new ClientException(LDAPResultCode.OTHER, msg, e);
} catch (CommunicationException e) {
Message msg = ERR_DSCFG_ERROR_CREATE_CE.get(ufn, e.getMessage());
throw new ClientException(LDAPResultCode.CLIENT_SIDE_SERVER_DOWN,
msg);
} catch (ManagedObjectNotFoundException e) {
Message pufn = path.getManagedObjectDefinition()
.getUserFriendlyName();
Message msg = ERR_DSCFG_ERROR_GET_PARENT_MONFE.get(pufn);
if (app.isInteractive()) {
app.println();
app.printVerboseMessage(msg);
return MenuResult.cancel();
} else {
throw new ClientException(LDAPResultCode.NO_SUCH_OBJECT, msg);
}
}
// Now let the user create the child component.
app.println();
app.println();
return CreateSubCommandHandler.createManagedObject(app, context,
parent, rd);
} catch (ClientException e) {
// FIXME: should really do something better with the exception
// handling here. For example, if a authz or communications
// exception occurs then the application should exit.
app.println();
app.println(e.getMessageObject());
app.println();
app.pressReturnToContinue();
return MenuResult.cancel();
}
}
}
/**
* A help call-back which displays a description and summary of a
* component and its properties.
*/
private static final class ComponentHelpCallback implements HelpCallback {
// The managed object being edited.
private final ManagedObject<?> mo;
// The properties that can be edited.
private final Collection<PropertyDefinition<?>> properties;
// Creates a new component helper for the specified property.
private ComponentHelpCallback(ManagedObject<?> mo,
Collection<PropertyDefinition<?>> c) {
this.mo = mo;
this.properties = c;
}
/**
* {@inheritDoc}
*/
public void display(ConsoleApplication app) {
app.println();
HelpSubCommandHandler.displaySingleComponent(app, mo
.getManagedObjectDefinition(), properties);
app.println();
app.pressReturnToContinue();
}
}
/**
* A simple interface for querying and retrieving common default
* behavior properties.
*/
private static final class DefaultBehaviorQuery<T> {
/**
* The type of default behavior.
*/
private enum Type {
/**
* Alias default behavior.
*/
ALIAS,
/**
* Defined default behavior.
*/
DEFINED,
/**
* Inherited default behavior.
*/
INHERITED,
/**
* Undefined default behavior.
*/
UNDEFINED;
};
/**
* Create a new default behavior query object based on the provied
* property definition.
*
* @param <T>
* The type of property definition.
* @param pd
* The property definition.
* @return The default behavior query object.
*/
public static <T> DefaultBehaviorQuery<T> query(PropertyDefinition<T> pd) {
DefaultBehaviorProviderVisitor<T, DefaultBehaviorQuery<T>,
PropertyDefinition<T>> visitor =
new DefaultBehaviorProviderVisitor<T, DefaultBehaviorQuery<T>,
PropertyDefinition<T>>() {
/**
* {@inheritDoc}
*/
public DefaultBehaviorQuery<T> visitAbsoluteInherited(
AbsoluteInheritedDefaultBehaviorProvider<T> d,
PropertyDefinition<T> p) {
AbstractManagedObjectDefinition<?, ?> mod = d
.getManagedObjectDefinition();
String propertyName = d.getPropertyName();
PropertyDefinition<?> pd2 = mod.getPropertyDefinition(propertyName);
DefaultBehaviorQuery<?> query = query(pd2);
return new DefaultBehaviorQuery<T>(Type.INHERITED, query
.getAliasDescription());
}
/**
* {@inheritDoc}
*/
public DefaultBehaviorQuery<T> visitAlias(
AliasDefaultBehaviorProvider<T> d, PropertyDefinition<T> p) {
return new DefaultBehaviorQuery<T>(Type.ALIAS, d.getSynopsis());
}
/**
* {@inheritDoc}
*/
public DefaultBehaviorQuery<T> visitDefined(
DefinedDefaultBehaviorProvider<T> d, PropertyDefinition<T> p) {
return new DefaultBehaviorQuery<T>(Type.DEFINED, null);
}
/**
* {@inheritDoc}
*/
public DefaultBehaviorQuery<T> visitRelativeInherited(
RelativeInheritedDefaultBehaviorProvider<T> d,
PropertyDefinition<T> p) {
AbstractManagedObjectDefinition<?, ?> mod = d
.getManagedObjectDefinition();
String propertyName = d.getPropertyName();
PropertyDefinition<?> pd2 = mod.getPropertyDefinition(propertyName);
DefaultBehaviorQuery<?> query = query(pd2);
return new DefaultBehaviorQuery<T>(Type.INHERITED, query
.getAliasDescription());
}
/**
* {@inheritDoc}
*/
public DefaultBehaviorQuery<T> visitUndefined(
UndefinedDefaultBehaviorProvider<T> d, PropertyDefinition<T> p) {
return new DefaultBehaviorQuery<T>(Type.UNDEFINED, null);
}
};
return pd.getDefaultBehaviorProvider().accept(visitor, pd);
}
// The description of the behavior if it is an alias default
// behavior.
private final Message aliasDescription;
// The type of behavior.
private final Type type;
// Private constructor.
private DefaultBehaviorQuery(Type type, Message aliasDescription) {
this.type = type;
this.aliasDescription = aliasDescription;
}
/**
* Gets the detailed description of this default behavior if it is
* an alias default behavior or if it inherits from an alias
* default behavior.
*
* @return Returns the detailed description of this default
* behavior if it is an alias default behavior or if it
* inherits from an alias default behavior, otherwise
* <code>null</code>.
*/
public Message getAliasDescription() {
return aliasDescription;
}
/**
* Determines whether or not the default behavior is alias.
*
* @return Returns <code>true</code> if the default behavior is
* alias.
*/
public boolean isAlias() {
return type == Type.ALIAS;
}
/**
* Determines whether or not the default behavior is defined.
*
* @return Returns <code>true</code> if the default behavior is
* defined.
*/
public boolean isDefined() {
return type == Type.DEFINED;
}
/**
* Determines whether or not the default behavior is inherited.
*
* @return Returns <code>true</code> if the default behavior is
* inherited.
*/
public boolean isInherited() {
return type == Type.INHERITED;
}
/**
* Determines whether or not the default behavior is undefined.
*
* @return Returns <code>true</code> if the default behavior is
* undefined.
*/
public boolean isUndefined() {
return type == Type.UNDEFINED;
}
}
/**
* A property definition visitor which initializes mandatory
* properties.
*/
private final class MandatoryPropertyInitializer extends
PropertyDefinitionVisitor<MenuResult<Void>, Void> implements
MenuCallback<Void> {
// Any exception that was caught during processing.
private CLIException e = null;
// The managed object being edited.
private final ManagedObject<?> mo;
// The property to be edited.
private final PropertyDefinition<?> pd;
// Creates a new property editor for the specified property.
private MandatoryPropertyInitializer(ManagedObject<?> mo,
PropertyDefinition<?> pd) {
this.mo = mo;
this.pd = pd;
}
/**
* {@inheritDoc}
*/
public MenuResult<Void> invoke(ConsoleApplication app)
throws CLIException {
displayPropertyHeader(app, pd);
MenuResult<Void> result = pd.accept(this, null);
if (e != null) {
throw e;
} else {
return result;
}
}
/**
* {@inheritDoc}
*/
@Override
public <C extends ConfigurationClient, S extends Configuration>
MenuResult<Void> visitAggregation(
AggregationPropertyDefinition<C, S> d, Void p) {
MenuBuilder<String> builder = new MenuBuilder<String>(app);
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
InstantiableRelationDefinition<C, S> rd = d.getRelationDefinition();
if (d.hasOption(PropertyOption.MULTI_VALUED)) {
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_COMPONENT_MULTI.get(rd
.getUserFriendlyPluralName(), d.getName()));
builder.setAllowMultiSelect(true);
} else {
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_COMPONENT_SINGLE.get(rd
.getUserFriendlyName(), d.getName()));
}
// Create a list of possible names.
Set<String> values = new TreeSet<String>(d);
ManagedObjectPath<?,?> path = d.getParentPath();
try {
values.addAll(Arrays.asList(context.listManagedObjects(path, rd)));
} catch (AuthorizationException e) {
this.e = new CLIException(e.getMessageObject());
return MenuResult.quit();
} catch (ManagedObjectNotFoundException e) {
this.e = new CLIException(e.getMessageObject());
return MenuResult.cancel();
} catch (CommunicationException e) {
this.e = new CLIException(e.getMessageObject());
return MenuResult.quit();
}
for (String value : values) {
Message option = getPropertyValues(d, Collections.singleton(value));
builder.addNumberedOption(option, MenuResult.success(value));
}
MenuCallback<String> callback = new CreateComponentCallback<C, S>(d);
builder.addNumberedOption(INFO_EDITOR_OPTION_CREATE_A_NEW_COMPONENT
.get(rd.getUserFriendlyName()), callback);
builder.addHelpOption(new PropertyHelpCallback(mo
.getManagedObjectDefinition(), d));
if (app.isMenuDrivenMode()) {
builder.addCancelOption(true);
}
builder.addQuitOption();
Menu<String> menu = builder.toMenu();
try {
app.println();
MenuResult<String> result = menu.run();
if (result.isQuit()) {
return MenuResult.quit();
} else if (result.isCancel()) {
return MenuResult.cancel();
} else {
Collection<String> newValues = result.getValues();
SortedSet<String> oldValues =
new TreeSet<String>(mo.getPropertyValues(d));
mo.setPropertyValues(d, newValues);
isLastChoiceReset = false;
registerModification(d, new TreeSet<String>(newValues),
oldValues);
return MenuResult.success();
}
} catch (CLIException e) {
this.e = e;
return MenuResult.cancel();
}
}
/**
* /** {@inheritDoc}
*/
@Override
public MenuResult<Void> visitBoolean(BooleanPropertyDefinition d, Void p) {
MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
builder
.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUE_SINGLE.get(d.getName()));
builder
.addNumberedOption(INFO_VALUE_TRUE.get(), MenuResult.success(true));
builder.addNumberedOption(INFO_VALUE_FALSE.get(), MenuResult
.success(false));
builder.addHelpOption(new PropertyHelpCallback(mo
.getManagedObjectDefinition(), d));
if (app.isMenuDrivenMode()) {
builder.addCancelOption(true);
}
builder.addQuitOption();
Menu<Boolean> menu = builder.toMenu();
try {
app.println();
MenuResult<Boolean> result = menu.run();
if (result.isQuit()) {
return MenuResult.quit();
} else if (result.isCancel()) {
return MenuResult.cancel();
} else {
Collection<Boolean> newValues = result.getValues();
SortedSet<Boolean> oldValues = new TreeSet<Boolean>(
mo.getPropertyValues(d));
mo.setPropertyValues(d, newValues);
isLastChoiceReset = false;
registerModification(d, new TreeSet<Boolean>(newValues),
oldValues);
return MenuResult.success();
}
} catch (CLIException e) {
this.e = e;
return MenuResult.cancel();
}
}
/**
* {@inheritDoc}
*/
@Override
public <E extends Enum<E>> MenuResult<Void> visitEnum(
EnumPropertyDefinition<E> d, Void x) {
MenuBuilder<E> builder = new MenuBuilder<E>(app);
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
if (d.hasOption(PropertyOption.MULTI_VALUED)) {
builder
.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUE_MULTI.get(d.getName()));
builder.setAllowMultiSelect(true);
} else {
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUE_SINGLE
.get(d.getName()));
}
Set<E> values = new TreeSet<E>(d);
values.addAll(EnumSet.allOf(d.getEnumClass()));
for (E value : values) {
Message option = getPropertyValues(d, Collections.singleton(value));
builder.addNumberedOption(option, MenuResult.success(value));
}
builder.addHelpOption(new PropertyHelpCallback(mo
.getManagedObjectDefinition(), d));
if (app.isMenuDrivenMode()) {
builder.addCancelOption(true);
}
builder.addQuitOption();
Menu<E> menu = builder.toMenu();
try {
app.println();
MenuResult<E> result = menu.run();
if (result.isQuit()) {
return MenuResult.quit();
} else if (result.isCancel()) {
return MenuResult.cancel();
} else {
Collection<E> newValues = result.getValues();
SortedSet<E> oldValues = new TreeSet<E>(mo.getPropertyValues(d));
mo.setPropertyValues(d, newValues);
isLastChoiceReset = false;
registerModification(d, new TreeSet<E>(newValues), oldValues);
return MenuResult.success();
}
} catch (CLIException e) {
this.e = e;
return MenuResult.cancel();
}
}
/**
* {@inheritDoc}
*/
@Override
public <T> MenuResult<Void> visitUnknown(PropertyDefinition<T> d,
Void p) throws UnknownPropertyDefinitionException {
app.println();
displayPropertySyntax(app, d);
// Set the new property value(s).
try {
SortedSet<T> values = readPropertyValues(app,
mo.getManagedObjectDefinition(), d);
SortedSet<T> oldValues = new TreeSet<T>(mo.getPropertyValues(d));
mo.setPropertyValues(d, values);
isLastChoiceReset = false;
registerModification(d, values, oldValues);
return MenuResult.success();
} catch (CLIException e) {
this.e = e;
return MenuResult.cancel();
}
}
}
/**
* A menu call-back for editing a modifiable multi-valued property.
*/
private final class MultiValuedPropertyEditor extends
PropertyDefinitionVisitor<MenuResult<Boolean>, Void>
implements MenuCallback<Boolean> {
// Any exception that was caught during processing.
private CLIException e = null;
// The managed object being edited.
private final ManagedObject<?> mo;
// The property to be edited.
private final PropertyDefinition<?> pd;
// Creates a new property editor for the specified property.
private MultiValuedPropertyEditor(ManagedObject<?> mo,
PropertyDefinition<?> pd) {
Validator.ensureTrue(pd.hasOption(PropertyOption.MULTI_VALUED));
this.mo = mo;
this.pd = pd;
}
/**
* {@inheritDoc}
*/
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
displayPropertyHeader(app, pd);
MenuResult<Boolean> result = pd.accept(this, null);
if (e != null) {
throw e;
} else {
return result;
}
}
/**
* {@inheritDoc}
*/
@Override
public <C extends ConfigurationClient, S extends Configuration>
MenuResult<Boolean> visitAggregation(
final AggregationPropertyDefinition<C, S> d, Void p) {
final SortedSet<String> defaultValues = mo.getPropertyDefaultValues(d);
final SortedSet<String> oldValues = mo.getPropertyValues(d);
final SortedSet<String> currentValues = mo.getPropertyValues(d);
final InstantiableRelationDefinition<C, S> rd = d.getRelationDefinition();
final Message ufpn = rd.getUserFriendlyPluralName();
boolean isFirst = true;
while (true) {
if (!isFirst) {
app.println();
app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_CONT.get(d
.getName()));
} else {
isFirst = false;
}
if (currentValues.size() > 1) {
app.println();
app.println(INFO_EDITOR_HEADING_COMPONENT_SUMMARY.get(d.getName(),
ufpn));
app.println();
displayPropertyValues(app, d, currentValues);
}
// Create a list of possible names.
final Set<String> values = new TreeSet<String>(d);
ManagedObjectPath<?,?> path = d.getParentPath();
try {
values.addAll(Arrays.asList(context.listManagedObjects(path, rd)));
} catch (AuthorizationException e) {
this.e = new CLIException(e.getMessageObject());
return MenuResult.quit();
} catch (ManagedObjectNotFoundException e) {
this.e = new CLIException(e.getMessageObject());
return MenuResult.cancel();
} catch (CommunicationException e) {
this.e = new CLIException(e.getMessageObject());
return MenuResult.quit();
}
// Create the add values call-back.
MenuCallback<Boolean> addCallback = null;
values.removeAll(currentValues);
if (!values.isEmpty()) {
addCallback = new MenuCallback<Boolean>() {
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
MenuBuilder<String> builder = new MenuBuilder<String>(app);
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_COMPONENTS_ADD
.get(ufpn));
builder.setAllowMultiSelect(true);
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
for (String value : values) {
Message svalue = getPropertyValues(d, Collections
.singleton(value));
builder.addNumberedOption(svalue, MenuResult.success(value));
}
MenuCallback<String> callback = new CreateComponentCallback<C, S>(
d);
builder.addNumberedOption(
INFO_EDITOR_OPTION_CREATE_A_NEW_COMPONENT.get(rd
.getUserFriendlyName()), callback);
if (values.size() > 1) {
// No point in having this option if there's only one
// possible value.
builder.addNumberedOption(INFO_EDITOR_OPTION_ADD_ALL_COMPONENTS
.get(ufpn), MenuResult.success(values));
}
builder.addHelpOption(new PropertyHelpCallback(mo
.getManagedObjectDefinition(), d));
builder.addCancelOption(true);
builder.addQuitOption();
app.println();
app.println();
Menu<String> menu = builder.toMenu();
MenuResult<String> result = menu.run();
if (result.isSuccess()) {
// Set the new property value(s).
Collection<String> addedValues = result.getValues();
currentValues.addAll(addedValues);
isLastChoiceReset = false;
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else if (result.isCancel()) {
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else {
return MenuResult.quit();
}
}
};
}
// Create the remove values call-back.
MenuCallback<Boolean> removeCallback = new MenuCallback<Boolean>() {
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
MenuBuilder<String> builder = new MenuBuilder<String>(app);
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_COMPONENTS_REMOVE
.get(ufpn));
builder.setAllowMultiSelect(true);
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
for (String value : currentValues) {
Message svalue = getPropertyValues(d, Collections
.singleton(value));
builder.addNumberedOption(svalue, MenuResult.success(value));
}
builder.addHelpOption(new PropertyHelpCallback(mo
.getManagedObjectDefinition(), d));
builder.addCancelOption(true);
builder.addQuitOption();
app.println();
app.println();
Menu<String> menu = builder.toMenu();
MenuResult<String> result = menu.run();
if (result.isSuccess()) {
// Set the new property value(s).
Collection<String> removedValues = result.getValues();
currentValues.removeAll(removedValues);
isLastChoiceReset = false;
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else if (result.isCancel()) {
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else {
return MenuResult.quit();
}
}
};
MenuResult<Boolean> result = runMenu(d, app, defaultValues, oldValues,
currentValues, addCallback, removeCallback);
if (!result.isAgain()) {
return result;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public <T extends Enum<T>> MenuResult<Boolean> visitEnum(
final EnumPropertyDefinition<T> d, Void p) {
final SortedSet<T> defaultValues = mo.getPropertyDefaultValues(d);
final SortedSet<T> oldValues = mo.getPropertyValues(d);
final SortedSet<T> currentValues = mo.getPropertyValues(d);
boolean isFirst = true;
while (true) {
if (!isFirst) {
app.println();
app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_CONT.get(d
.getName()));
} else {
isFirst = false;
}
if (currentValues.size() > 1) {
app.println();
app.println(INFO_EDITOR_HEADING_VALUES_SUMMARY.get(d.getName()));
app.println();
displayPropertyValues(app, d, currentValues);
}
// Create the add values call-back.
MenuCallback<Boolean> addCallback = null;
final EnumSet<T> values = EnumSet.allOf(d.getEnumClass());
values.removeAll(currentValues);
if (!values.isEmpty()) {
addCallback = new MenuCallback<Boolean>() {
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
MenuBuilder<T> builder = new MenuBuilder<T>(app);
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUES_ADD.get());
builder.setAllowMultiSelect(true);
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
for (T value : values) {
Message svalue = getPropertyValues(d, Collections
.singleton(value));
builder.addNumberedOption(svalue, MenuResult.success(value));
}
if (values.size() > 1) {
// No point in having this option if there's only one
// possible value.
builder.addNumberedOption(INFO_EDITOR_OPTION_ADD_ALL_VALUES
.get(), MenuResult.success(values));
}
builder.addHelpOption(new PropertyHelpCallback(mo
.getManagedObjectDefinition(), d));
builder.addCancelOption(true);
builder.addQuitOption();
app.println();
app.println();
Menu<T> menu = builder.toMenu();
MenuResult<T> result = menu.run();
if (result.isSuccess()) {
// Set the new property value(s).
Collection<T> addedValues = result.getValues();
currentValues.addAll(addedValues);
isLastChoiceReset = false;
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else if (result.isCancel()) {
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else {
return MenuResult.quit();
}
}
};
}
// Create the remove values call-back.
MenuCallback<Boolean> removeCallback = new MenuCallback<Boolean>() {
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
MenuBuilder<T> builder = new MenuBuilder<T>(app);
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUES_REMOVE.get());
builder.setAllowMultiSelect(true);
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
for (T value : currentValues) {
Message svalue = getPropertyValues(d, Collections
.singleton(value));
builder.addNumberedOption(svalue, MenuResult.success(value));
}
builder.addHelpOption(new PropertyHelpCallback(mo
.getManagedObjectDefinition(), d));
builder.addCancelOption(true);
builder.addQuitOption();
app.println();
app.println();
Menu<T> menu = builder.toMenu();
MenuResult<T> result = menu.run();
if (result.isSuccess()) {
// Set the new property value(s).
Collection<T> removedValues = result.getValues();
currentValues.removeAll(removedValues);
isLastChoiceReset = false;
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else if (result.isCancel()) {
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else {
return MenuResult.quit();
}
}
};
MenuResult<Boolean> result = runMenu(d, app, defaultValues, oldValues,
currentValues, addCallback, removeCallback);
if (!result.isAgain()) {
return result;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public <T> MenuResult<Boolean> visitUnknown(final PropertyDefinition<T> d,
Void p) {
app.println();
displayPropertySyntax(app, d);
final SortedSet<T> defaultValues = mo.getPropertyDefaultValues(d);
final SortedSet<T> oldValues = mo.getPropertyValues(d);
final SortedSet<T> currentValues = mo.getPropertyValues(d);
boolean isFirst = true;
while (true) {
if (!isFirst) {
app.println();
app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_CONT.get(d
.getName()));
} else {
isFirst = false;
}
if (currentValues.size() > 1) {
app.println();
app.println(INFO_EDITOR_HEADING_VALUES_SUMMARY.get(d.getName()));
app.println();
displayPropertyValues(app, d, currentValues);
}
// Create the add values call-back.
MenuCallback<Boolean> addCallback = new MenuCallback<Boolean>() {
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
app.println();
SortedSet<T> previousValues = new TreeSet<T>(currentValues);
readPropertyValues(app, mo.getManagedObjectDefinition(), d,
currentValues);
SortedSet<T> addedValues = new TreeSet<T>(currentValues);
addedValues.removeAll(previousValues);
isLastChoiceReset = false;
return MenuResult.success(false);
}
};
// Create the remove values call-back.
MenuCallback<Boolean> removeCallback = new MenuCallback<Boolean>() {
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
MenuBuilder<T> builder = new MenuBuilder<T>(app);
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUES_REMOVE.get());
builder.setAllowMultiSelect(true);
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
for (T value : currentValues) {
Message svalue = getPropertyValues(d, Collections
.singleton(value));
builder.addNumberedOption(svalue, MenuResult.success(value));
}
builder.addHelpOption(new PropertyHelpCallback(mo
.getManagedObjectDefinition(), d));
builder.addCancelOption(true);
builder.addQuitOption();
app.println();
app.println();
Menu<T> menu = builder.toMenu();
MenuResult<T> result = menu.run();
if (result.isSuccess()) {
// Set the new property value(s).
Collection<T> removedValues = result.getValues();
currentValues.removeAll(removedValues);
isLastChoiceReset = false;
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else if (result.isCancel()) {
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else {
return MenuResult.quit();
}
}
};
MenuResult<Boolean> result = runMenu(d, app, defaultValues, oldValues,
currentValues, addCallback, removeCallback);
if (!result.isAgain()) {
return result;
}
}
}
/**
* Generate an appropriate menu option for a property which asks
* the user whether or not they want to keep the property's
* current settings.
*/
private <T> Message getKeepDefaultValuesMenuOption(
PropertyDefinition<T> pd, SortedSet<T> defaultValues,
SortedSet<T> oldValues, SortedSet<T> currentValues) {
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
boolean isModified = !currentValues.equals(oldValues);
boolean isDefault = currentValues.equals(defaultValues);
if (isModified) {
switch (currentValues.size()) {
case 0:
if (query.isAlias()) {
return INFO_EDITOR_OPTION_USE_DEFAULT_ALIAS.get(query
.getAliasDescription());
} else if (query.isInherited()) {
if (query.getAliasDescription() != null) {
return INFO_EDITOR_OPTION_USE_DEFAULT_INHERITED_ALIAS.get(query
.getAliasDescription());
} else {
return INFO_EDITOR_OPTION_USE_DEFAULT_INHERITED_ALIAS_UNDEFINED
.get();
}
} else {
return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
}
case 1:
Message svalue = getPropertyValues(pd, currentValues);
if (isDefault) {
if (query.isInherited()) {
return INFO_EDITOR_OPTION_USE_INHERITED_DEFAULT_VALUE.get(svalue);
} else {
return INFO_EDITOR_OPTION_USE_DEFAULT_VALUE.get(svalue);
}
} else {
return INFO_EDITOR_OPTION_USE_VALUE.get(svalue);
}
default:
if (isDefault) {
if (query.isInherited()) {
return INFO_EDITOR_OPTION_USE_INHERITED_DEFAULT_VALUES.get();
} else {
return INFO_EDITOR_OPTION_USE_DEFAULT_VALUES.get();
}
} else {
return INFO_EDITOR_OPTION_USE_VALUES.get();
}
}
} else {
switch (currentValues.size()) {
case 0:
if (query.isAlias()) {
return INFO_EDITOR_OPTION_KEEP_DEFAULT_ALIAS.get(query
.getAliasDescription());
} else if (query.isInherited()) {
if (query.getAliasDescription() != null) {
return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS.get(query
.getAliasDescription());
} else {
return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS_UNDEFINED
.get();
}
} else {
return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
}
case 1:
Message svalue = getPropertyValues(pd, currentValues);
if (isDefault) {
if (query.isInherited()) {
return INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUE
.get(svalue);
} else {
return INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUE.get(svalue);
}
} else {
return INFO_EDITOR_OPTION_KEEP_VALUE.get(svalue);
}
default:
if (isDefault) {
if (query.isInherited()) {
return INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUES.get();
} else {
return INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUES.get();
}
} else {
return INFO_EDITOR_OPTION_KEEP_VALUES.get();
}
}
}
}
/**
* Generate an appropriate menu option which should be used in the
* case where a property can be reset to its default behavior.
*/
private <T> Message getResetToDefaultValuesMenuOption(
PropertyDefinition<T> pd, SortedSet<T> defaultValues,
SortedSet<T> currentValues) {
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
boolean isMandatory = pd.hasOption(PropertyOption.MANDATORY);
if (!isMandatory && query.isAlias()) {
return INFO_EDITOR_OPTION_RESET_DEFAULT_ALIAS.get(query
.getAliasDescription());
} else if (query.isDefined()) {
// Only show this option if the current value is different
// to the default.
if (!currentValues.equals(defaultValues)) {
Message svalue = getPropertyValues(pd, defaultValues);
if (defaultValues.size() > 1) {
return INFO_EDITOR_OPTION_RESET_DEFAULT_VALUES.get(svalue);
} else {
return INFO_EDITOR_OPTION_RESET_DEFAULT_VALUE.get(svalue);
}
} else {
return null;
}
} else if (!isMandatory && query.isInherited()) {
if (defaultValues.isEmpty()) {
if (query.getAliasDescription() != null) {
return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS.get(query
.getAliasDescription());
} else {
return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS_UNDEFINED
.get();
}
} else {
Message svalue = getPropertyValues(pd, defaultValues);
if (defaultValues.size() > 1) {
return INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUES
.get(svalue);
} else {
return INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUE.get(svalue);
}
}
} else if (!isMandatory && query.isUndefined()) {
return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
} else {
return null;
}
}
// Common menu processing.
private <T> MenuResult<Boolean> runMenu(final PropertyDefinition<T> d,
ConsoleApplication app, final SortedSet<T> defaultValues,
final SortedSet<T> oldValues, final SortedSet<T> currentValues,
MenuCallback<Boolean> addCallback,
MenuCallback<Boolean> removeCallback) {
// Construct a menu of actions.
MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
// First option is for leaving the property unchanged or
// applying changes, but only if the state of the property is
// valid.
if (!(d.hasOption(PropertyOption.MANDATORY) && currentValues.isEmpty())) {
MenuResult<Boolean> result;
if (!oldValues.equals(currentValues)) {
result = MenuResult.success(true);
} else {
result = MenuResult.<Boolean> cancel();
}
Message option = getKeepDefaultValuesMenuOption(d, defaultValues,
oldValues, currentValues);
builder.addNumberedOption(option, result);
builder.setDefault(Message.raw("1"), result);
}
// Add an option for adding some values.
if (addCallback != null) {
int i = builder.addNumberedOption(
INFO_EDITOR_OPTION_ADD_ONE_OR_MORE_VALUES.get(), addCallback);
if (d.hasOption(PropertyOption.MANDATORY) && currentValues.isEmpty()) {
builder.setDefault(Message.raw("%d", i), addCallback);
}
}
// Add options for removing values if applicable.
if (!currentValues.isEmpty()) {
builder.addNumberedOption(INFO_EDITOR_OPTION_REMOVE_ONE_OR_MORE_VALUES
.get(), removeCallback);
}
// Add options for removing all values and for resetting the
// property to its default behavior.
Message resetOption = null;
if (!currentValues.equals(defaultValues)) {
resetOption = getResetToDefaultValuesMenuOption(d, defaultValues,
currentValues);
}
if (!currentValues.isEmpty()) {
if (resetOption == null || !defaultValues.isEmpty()) {
MenuCallback<Boolean> callback = new MenuCallback<Boolean>() {
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
isLastChoiceReset = false;
currentValues.clear();
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
}
};
builder.addNumberedOption(INFO_EDITOR_OPTION_REMOVE_ALL_VALUES.get(),
callback);
}
}
if (resetOption != null) {
MenuCallback<Boolean> callback = new MenuCallback<Boolean>() {
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
currentValues.clear();
currentValues.addAll(defaultValues);
isLastChoiceReset = true;
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
}
};
builder.addNumberedOption(resetOption, callback);
}
// Add an option for undoing any changes.
if (!oldValues.equals(currentValues)) {
MenuCallback<Boolean> callback = new MenuCallback<Boolean>() {
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
currentValues.clear();
currentValues.addAll(oldValues);
isLastChoiceReset = false;
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
}
};
builder.addNumberedOption(INFO_EDITOR_OPTION_REVERT_CHANGES.get(),
callback);
}
builder.addHelpOption(new PropertyHelpCallback(mo
.getManagedObjectDefinition(), d));
builder.addQuitOption();
Menu<Boolean> menu = builder.toMenu();
MenuResult<Boolean> result;
try {
app.println();
result = menu.run();
} catch (CLIException e) {
this.e = e;
return null;
}
if (result.isSuccess()) {
if (result.getValue() == true) {
// Set the new property value(s).
mo.setPropertyValues(d, currentValues);
registerModification(d, currentValues, oldValues);
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else {
// Continue until cancel/apply changes.
app.println();
return MenuResult.again();
}
} else if (result.isCancel()) {
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else {
return MenuResult.quit();
}
}
}
/**
* A help call-back which displays a description and summary of a
* single property.
*/
private static final class PropertyHelpCallback implements HelpCallback {
// The managed object definition.
private final ManagedObjectDefinition<?, ?> d;
// The property to be edited.
private final PropertyDefinition<?> pd;
// Creates a new property helper for the specified property.
private PropertyHelpCallback(ManagedObjectDefinition<?, ?> d,
PropertyDefinition<?> pd) {
this.d = d;
this.pd = pd;
}
/**
* {@inheritDoc}
*/
public void display(ConsoleApplication app) {
app.println();
HelpSubCommandHandler.displayVerboseSingleProperty(app, d, pd.getName());
app.println();
app.pressReturnToContinue();
}
}
/**
* A menu call-back for viewing a read-only properties.
*/
private final class ReadOnlyPropertyViewer extends
PropertyDefinitionVisitor<MenuResult<Boolean>, Void> implements
MenuCallback<Boolean> {
// Any exception that was caught during processing.
private CLIException e = null;
// The managed object being edited.
private final ManagedObject<?> mo;
// The property to be edited.
private final PropertyDefinition<?> pd;
// Creates a new property editor for the specified property.
private ReadOnlyPropertyViewer(ManagedObject<?> mo,
PropertyDefinition<?> pd) {
this.mo = mo;
this.pd = pd;
}
/**
* {@inheritDoc}
*/
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
MenuResult<Boolean> result = pd.accept(this, null);
if (e != null) {
throw e;
} else {
return result;
}
}
/**
* {@inheritDoc}
*/
@Override
public <T> MenuResult<Boolean> visitUnknown(PropertyDefinition<T> pd,
Void p) {
SortedSet<T> values = mo.getPropertyValues(pd);
app.println();
app.println();
switch (values.size()) {
case 0:
// Only alias, undefined, or inherited alias or undefined
// properties should apply here.
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
Message aliasDescription = query.getAliasDescription();
if (aliasDescription == null) {
app.println(INFO_EDITOR_HEADING_READ_ONLY_ALIAS_UNDEFINED.get(pd
.getName()));
} else {
app.println(INFO_EDITOR_HEADING_READ_ONLY_ALIAS.get(pd.getName(),
aliasDescription));
}
break;
case 1:
Message svalue = getPropertyValues(pd, mo);
app.println(INFO_EDITOR_HEADING_READ_ONLY_VALUE.get(pd.getName(),
svalue));
break;
default:
app.println(INFO_EDITOR_HEADING_READ_ONLY_VALUES.get(pd.getName()));
app.println();
displayPropertyValues(app, pd, values);
break;
}
app.println();
boolean result;
try {
result = app.confirmAction(INFO_EDITOR_PROMPT_READ_ONLY.get(), false);
} catch (CLIException e) {
this.e = e;
return null;
}
if (result) {
app.println();
HelpSubCommandHandler.displayVerboseSingleProperty(app, mo
.getManagedObjectDefinition(), pd.getName());
app.println();
app.pressReturnToContinue();
}
return MenuResult.again();
}
}
/**
* A menu call-back for editing a modifiable single-valued property.
*/
private final class SingleValuedPropertyEditor extends
PropertyDefinitionVisitor<MenuResult<Boolean>, Void>
implements MenuCallback<Boolean> {
// Any exception that was caught during processing.
private CLIException e = null;
// The managed object being edited.
private final ManagedObject<?> mo;
// The property to be edited.
private final PropertyDefinition<?> pd;
// Creates a new property editor for the specified property.
private SingleValuedPropertyEditor(ManagedObject<?> mo,
PropertyDefinition<?> pd) {
Validator.ensureTrue(!pd.hasOption(PropertyOption.MULTI_VALUED));
this.mo = mo;
this.pd = pd;
}
/**
* {@inheritDoc}
*/
public MenuResult<Boolean> invoke(ConsoleApplication app)
throws CLIException {
displayPropertyHeader(app, pd);
MenuResult<Boolean> result = pd.accept(this, null);
if (e != null) {
throw e;
} else {
return result;
}
}
/**
* {@inheritDoc}
*/
@Override
public <C extends ConfigurationClient, S extends Configuration>
MenuResult<Boolean> visitAggregation(
AggregationPropertyDefinition<C, S> d, Void p) {
// Construct a menu of actions.
MenuBuilder<String> builder = new MenuBuilder<String>(app);
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
DefaultBehaviorQuery<String> query = DefaultBehaviorQuery.query(d);
SortedSet<String> currentValues = mo.getPropertyValues(d);
SortedSet<String> defaultValues = mo.getPropertyDefaultValues(d);
String currentValue = currentValues.isEmpty() ? null : currentValues
.first();
String defaultValue = defaultValues.isEmpty() ? null : defaultValues
.first();
// First option is for leaving the property unchanged.
Message option = getKeepDefaultValuesMenuOption(d);
builder.addNumberedOption(option, MenuResult.<String> cancel());
builder.setDefault(Message.raw("1"), MenuResult.<String> cancel());
// Create a list of possible names.
final Set<String> values = new TreeSet<String>(d);
ManagedObjectPath<?,?> path = d.getParentPath();
InstantiableRelationDefinition<C, S> rd = d.getRelationDefinition();
try {
values.addAll(Arrays.asList(context.listManagedObjects(path, rd)));
} catch (AuthorizationException e) {
this.e = new CLIException(e.getMessageObject());
return MenuResult.quit();
} catch (ManagedObjectNotFoundException e) {
this.e = new CLIException(e.getMessageObject());
return MenuResult.cancel();
} catch (CommunicationException e) {
this.e = new CLIException(e.getMessageObject());
return MenuResult.quit();
}
final Message ufn = rd.getUserFriendlyName();
for (String value : values) {
if (currentValue != null && d.compare(value, currentValue) == 0) {
// This option is unnecessary.
continue;
}
Message svalue = getPropertyValues(d, Collections.singleton(value));
if (value.equals(defaultValue) && query.isDefined()) {
option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_COMPONENT.get(ufn,
svalue);
} else {
option = INFO_EDITOR_OPTION_CHANGE_TO_COMPONENT.get(ufn, svalue);
}
builder.addNumberedOption(option, MenuResult.success(value));
}
MenuCallback<String> callback = new CreateComponentCallback<C, S>(d);
builder.addNumberedOption(INFO_EDITOR_OPTION_CREATE_A_NEW_COMPONENT
.get(ufn), callback);
// Third option is to reset the value back to its default.
if (mo.isPropertyPresent(d) && !query.isDefined()) {
option = getResetToDefaultValuesMenuOption(d);
if (option != null) {
builder.addNumberedOption(option, MenuResult.<String> success());
}
}
return runMenu(d, builder);
}
/**
* {@inheritDoc}
*/
@Override
public MenuResult<Boolean> visitBoolean(BooleanPropertyDefinition d,
Void p) {
// Construct a menu of actions.
MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
DefaultBehaviorQuery<Boolean> query = DefaultBehaviorQuery.query(d);
SortedSet<Boolean> currentValues = mo.getPropertyValues(d);
SortedSet<Boolean> defaultValues = mo.getPropertyDefaultValues(d);
Boolean currentValue = currentValues.isEmpty() ? null : currentValues
.first();
Boolean defaultValue = defaultValues.isEmpty() ? null : defaultValues
.first();
// First option is for leaving the property unchanged.
Message option = getKeepDefaultValuesMenuOption(d);
builder.addNumberedOption(option, MenuResult.<Boolean> cancel());
builder.setDefault(Message.raw("1"), MenuResult.<Boolean> cancel());
// The second (and possibly third) option is to always change
// the property's value.
if (currentValue == null || currentValue == false) {
Message svalue = getPropertyValues(d, Collections.singleton(true));
if (defaultValue != null && defaultValue == true) {
option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_VALUE.get(svalue);
} else {
option = INFO_EDITOR_OPTION_CHANGE_TO_VALUE.get(svalue);
}
builder.addNumberedOption(option, MenuResult.success(true));
}
if (currentValue == null || currentValue == true) {
Message svalue = getPropertyValues(d, Collections.singleton(false));
if (defaultValue != null && defaultValue == false) {
option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_VALUE.get(svalue);
} else {
option = INFO_EDITOR_OPTION_CHANGE_TO_VALUE.get(svalue);
}
builder.addNumberedOption(option, MenuResult.success(false));
}
// Final option is to reset the value back to its default.
if (mo.isPropertyPresent(d) && !query.isDefined()) {
option = getResetToDefaultValuesMenuOption(d);
if (option != null) {
builder.addNumberedOption(option, MenuResult.<Boolean> success());
}
}
return runMenu(d, builder);
}
/**
* {@inheritDoc}
*/
@Override
public <E extends Enum<E>> MenuResult<Boolean> visitEnum(
EnumPropertyDefinition<E> d, Void p) {
// Construct a menu of actions.
MenuBuilder<E> builder = new MenuBuilder<E>(app);
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
DefaultBehaviorQuery<E> query = DefaultBehaviorQuery.query(d);
SortedSet<E> currentValues = mo.getPropertyValues(d);
SortedSet<E> defaultValues = mo.getPropertyDefaultValues(d);
E currentValue = currentValues.isEmpty() ? null : currentValues.first();
E defaultValue = defaultValues.isEmpty() ? null : defaultValues.first();
// First option is for leaving the property unchanged.
Message option = getKeepDefaultValuesMenuOption(d);
builder.addNumberedOption(option, MenuResult.<E> cancel());
builder.setDefault(Message.raw("1"), MenuResult.<E> cancel());
// Create options for changing to other values.
Set<E> values = new TreeSet<E>(d);
values.addAll(EnumSet.allOf(d.getEnumClass()));
for (E value : values) {
if (value.equals(currentValue) && query.isDefined()) {
// This option is unnecessary.
continue;
}
Message svalue = getPropertyValues(d, Collections.singleton(value));
if (value.equals(defaultValue) && query.isDefined()) {
option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_VALUE.get(svalue);
} else {
option = INFO_EDITOR_OPTION_CHANGE_TO_VALUE.get(svalue);
}
builder.addNumberedOption(option, MenuResult.success(value));
}
// Third option is to reset the value back to its default.
if (mo.isPropertyPresent(d) && !query.isDefined()) {
option = getResetToDefaultValuesMenuOption(d);
if (option != null) {
builder.addNumberedOption(option, MenuResult.<E> success());
}
}
return runMenu(d, builder);
}
/**
* {@inheritDoc}
*/
@Override
public <T> MenuResult<Boolean> visitUnknown(final PropertyDefinition<T> d,
Void p) {
app.println();
displayPropertySyntax(app, d);
// Construct a menu of actions.
MenuBuilder<T> builder = new MenuBuilder<T>(app);
builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
// First option is for leaving the property unchanged.
Message option = getKeepDefaultValuesMenuOption(d);
builder.addNumberedOption(option, MenuResult.<T> cancel());
builder.setDefault(Message.raw("1"), MenuResult.<T> cancel());
// The second option is to always change the property's value.
builder.addNumberedOption(INFO_EDITOR_OPTION_CHANGE_VALUE.get(),
new MenuCallback<T>() {
public MenuResult<T> invoke(ConsoleApplication app)
throws CLIException {
app.println();
Set<T> values = readPropertyValues(app, mo
.getManagedObjectDefinition(), d);
return MenuResult.success(values);
}
});
// Third option is to reset the value back to its default.
if (mo.isPropertyPresent(d)) {
option = getResetToDefaultValuesMenuOption(d);
if (option != null) {
builder.addNumberedOption(option, MenuResult.<T> success());
}
}
return runMenu(d, builder);
}
/**
* Generate an appropriate menu option for a property which asks
* the user whether or not they want to keep the property's
* current settings.
*/
private <T> Message getKeepDefaultValuesMenuOption(
PropertyDefinition<T> pd) {
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
SortedSet<T> currentValues = mo.getPropertyValues(pd);
SortedSet<T> defaultValues = mo.getPropertyDefaultValues(pd);
if (query.isDefined() && currentValues.equals(defaultValues)) {
Message svalue = getPropertyValues(pd, currentValues);
return INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUE.get(svalue);
} else if (mo.isPropertyPresent(pd)) {
Message svalue = getPropertyValues(pd, currentValues);
return INFO_EDITOR_OPTION_KEEP_VALUE.get(svalue);
} else if (query.isAlias()) {
return INFO_EDITOR_OPTION_KEEP_DEFAULT_ALIAS.get(query
.getAliasDescription());
} else if (query.isInherited()) {
if (defaultValues.isEmpty()) {
if (query.getAliasDescription() != null) {
return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS.get(query
.getAliasDescription());
} else {
return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS_UNDEFINED
.get();
}
} else {
Message svalue = getPropertyValues(pd, defaultValues);
return INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUE.get(svalue);
}
} else {
return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
}
}
/**
* Generate an appropriate menu option which should be used in the
* case where a property can be reset to its default behavior.
*/
private <T> Message getResetToDefaultValuesMenuOption(
PropertyDefinition<T> pd) {
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
SortedSet<T> currentValues = mo.getPropertyValues(pd);
SortedSet<T> defaultValues = mo.getPropertyDefaultValues(pd);
boolean isMandatory = pd.hasOption(PropertyOption.MANDATORY);
if (!isMandatory && query.isAlias()) {
return INFO_EDITOR_OPTION_RESET_DEFAULT_ALIAS.get(query
.getAliasDescription());
} else if (query.isDefined()) {
// Only show this option if the current value is different
// to the default.
if (!currentValues.equals(defaultValues)) {
Message svalue = getPropertyValues(pd, defaultValues);
return INFO_EDITOR_OPTION_RESET_DEFAULT_VALUE.get(svalue);
} else {
return null;
}
} else if (!isMandatory && query.isInherited()) {
if (defaultValues.isEmpty()) {
if (query.getAliasDescription() != null) {
return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS.get(query
.getAliasDescription());
} else {
return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS_UNDEFINED
.get();
}
} else {
Message svalue = getPropertyValues(pd, defaultValues);
return INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUE.get(svalue);
}
} else if (!isMandatory && query.isUndefined()) {
return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
} else {
return null;
}
}
// Common menu processing.
private <T> MenuResult<Boolean> runMenu(final PropertyDefinition<T> d,
MenuBuilder<T> builder) throws IllegalPropertyValueException,
PropertyIsSingleValuedException, PropertyIsReadOnlyException,
PropertyIsMandatoryException, IllegalArgumentException {
builder.addHelpOption(new PropertyHelpCallback(mo
.getManagedObjectDefinition(), d));
builder.addQuitOption();
Menu<T> menu = builder.toMenu();
MenuResult<T> result;
try {
app.println();
result = menu.run();
} catch (CLIException e) {
this.e = e;
return null;
}
if (result.isSuccess()) {
// Set the new property value(s).
Collection<T> newValues = result.getValues();
SortedSet<T> oldValues = new TreeSet<T>(mo.getPropertyValues(d));
mo.setPropertyValues(d, newValues);
if (newValues.size() > 0)
{
isLastChoiceReset = false;
}
else
{
// There are no newValues when we do a reset.
isLastChoiceReset = true;
}
registerModification(d, new TreeSet<T>(newValues), oldValues);
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else if (result.isCancel()) {
app.println();
app.pressReturnToContinue();
return MenuResult.success(false);
} else {
return MenuResult.quit();
}
}
}
// Display a title and a description of the property.
private static void displayPropertyHeader(ConsoleApplication app,
PropertyDefinition<?> pd) {
app.println();
app.println();
app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY.get(pd.getName()));
app.println();
app.printErrln(pd.getSynopsis(), 4);
if (pd.getDescription() != null) {
app.println();
app.printErrln(pd.getDescription(), 4);
}
}
// Display a property's syntax.
private static <T> void displayPropertySyntax(ConsoleApplication app,
PropertyDefinition<T> d) throws IllegalArgumentException {
PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(true);
TableBuilder builder = new TableBuilder();
builder.startRow();
builder.appendCell(INFO_EDITOR_HEADING_SYNTAX.get());
builder.appendCell(b.getUsage(d));
TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
printer.setDisplayHeadings(false);
printer.setIndentWidth(4);
printer.setColumnWidth(1, 0);
builder.print(printer);
}
// Display a table of property values.
private static <T> void displayPropertyValues(ConsoleApplication app,
PropertyDefinition<T> pd, Collection<T> values)
throws IllegalArgumentException {
TableBuilder builder = new TableBuilder();
PropertyValuePrinter valuePrinter = new PropertyValuePrinter(null,
null, false);
int sz = values.size();
boolean useMultipleColumns = (sz >= MULTI_COLUMN_THRESHOLD);
int rows = sz;
if (useMultipleColumns) {
// Display in two columns the first column should contain
// half the values. If there are an odd number of columns
// then the first column should contain an additional value
// (e.g. if there are 23 values, the first column should
// contain 12 values and the second column 11 values).
rows /= 2;
rows += sz % 2;
}
List<T> vl = new ArrayList<T>(values);
for (int i = 0, j = rows; i < rows; i++, j++) {
builder.startRow();
builder.appendCell("*)");
builder.appendCell(valuePrinter.print(pd, vl.get(i)));
if (useMultipleColumns && (j < sz)) {
builder.appendCell();
builder.appendCell("*)");
builder.appendCell(valuePrinter.print(pd, vl.get(j)));
}
}
TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
printer.setDisplayHeadings(false);
printer.setIndentWidth(4);
printer.setColumnWidth(1, 0);
if (useMultipleColumns) {
printer.setColumnWidth(2, 2);
printer.setColumnWidth(4, 0);
}
builder.print(printer);
}
// Display the set of values associated with a property.
private static <T> Message getPropertyValues(PropertyDefinition<T> pd,
Collection<T> values) {
if (values.isEmpty()) {
// There are no values or default values. Display the default
// behavior for alias values.
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
Message content = query.getAliasDescription();
if (content == null) {
return Message.raw("-");
} else {
return content;
}
} else {
PropertyValuePrinter printer =
new PropertyValuePrinter(null, null, false);
MessageBuilder builder = new MessageBuilder();
boolean isFirst = true;
for (T value : values) {
if (!isFirst) {
builder.append(", ");
}
builder.append(printer.print(pd, value));
isFirst = false;
}
return builder.toMessage();
}
}
// Display the set of values associated with a property.
private static <T> Message getPropertyValues(
PropertyDefinition<T> pd,
ManagedObject<?> mo) {
SortedSet<T> values = mo.getPropertyValues(pd);
return getPropertyValues(pd, values);
}
// Read new values for a property.
private static <T> SortedSet<T> readPropertyValues(ConsoleApplication app,
ManagedObjectDefinition<?, ?> d, PropertyDefinition<T> pd)
throws CLIException {
SortedSet<T> values = new TreeSet<T>(pd);
readPropertyValues(app, d, pd, values);
return values;
}
// Add values to a property.
private static <T> void readPropertyValues(ConsoleApplication app,
ManagedObjectDefinition<?, ?> d, PropertyDefinition<T> pd,
SortedSet<T> values) throws CLIException {
// Make sure there is at least one value if mandatory and empty.
if (values.isEmpty()) {
while (true) {
try {
Message prompt;
if (pd.hasOption(PropertyOption.MANDATORY)) {
prompt = INFO_EDITOR_PROMPT_READ_FIRST_VALUE.get(pd.getName());
} else {
prompt = INFO_EDITOR_PROMPT_READ_FIRST_VALUE_OPTIONAL.get(pd
.getName());
}
app.println();
String s = app.readLineOfInput(prompt);
if (s.trim().length() == 0) {
if (!pd.hasOption(PropertyOption.MANDATORY)) {
return;
}
}
T value = pd.decodeValue(s);
if (values.contains(value)) {
// Prevent addition of duplicates.
app.println();
app.println(ERR_EDITOR_READ_FIRST_DUPLICATE.get(s));
} else {
values.add(value);
}
break;
} catch (IllegalPropertyValueStringException e) {
app.println();
app.println(ArgumentExceptionFactory.adaptPropertyException(e, d)
.getMessageObject());
}
}
}
if (pd.hasOption(PropertyOption.MULTI_VALUED)) {
// Prompt for more values if multi-valued.
while (true) {
try {
Message prompt = INFO_EDITOR_PROMPT_READ_NEXT_VALUE.get(pd.getName());
app.println();
String s = app.readLineOfInput(prompt);
if (s.trim().length() == 0) {
return;
}
T value = pd.decodeValue(s);
if (values.contains(value)) {
// Prevent addition of duplicates.
app.println();
app.println(ERR_EDITOR_READ_NEXT_DUPLICATE.get(s));
} else {
values.add(value);
}
} catch (IllegalPropertyValueStringException e) {
app.println();
app.println(ArgumentExceptionFactory.adaptPropertyException(e, d)
.getMessageObject());
app.println();
}
}
}
}
// The threshold above which choice menus should be displayed in
// multiple columns.
private static final int MULTI_COLUMN_THRESHOLD = 8;
// The application console.
private final ConsoleApplication app;
// The management context.
private final ManagementContext context;
// The modifications performed: we assume that at most there is one
// modification per property definition.
private final List<PropertyEditorModification<?>> mods =
new ArrayList<PropertyEditorModification<?>>();
// Whether the last type of choice made by the user in a menu is a
// reset
private boolean isLastChoiceReset;
/**
* Create a new property value editor which will read from the
* provided application console.
*
* @param app
* The application console.
* @param context
* The management context.
*/
public PropertyValueEditor(ConsoleApplication app,
ManagementContext context) {
this.app = app;
this.context = context;
}
/**
* Interactively edits 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.
* @param isCreate
* Flag indicating whether or not the managed object is
* being created. If it is then read-only properties will
* be modifiable.
* @return Returns {@code MenuResult.success()} if the changes made
* to the managed object should be applied, or
* {@code MenuResult.cancel()} if the user to chose to
* cancel any changes, or {@code MenuResult.quit()} if the
* user chose to quit the application.
* @throws CLIException
* If the user input could not be retrieved for some
* reason.
*/
public MenuResult<Void> edit(ManagedObject<?> mo,
Collection<PropertyDefinition<?>> c, boolean isCreate)
throws CLIException {
// Get values for this missing mandatory property.
for (PropertyDefinition<?> pd : c) {
if (pd.hasOption(PropertyOption.MANDATORY)) {
if (mo.getPropertyValues(pd).isEmpty()) {
MandatoryPropertyInitializer mpi = new MandatoryPropertyInitializer(
mo, pd);
MenuResult<Void> result = mpi.invoke(app);
if (!result.isSuccess()) {
return result;
}
}
}
}
while (true) {
// Construct the main menu.
MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
Message ufn = mo.getManagedObjectDefinition().getUserFriendlyName();
builder.setPrompt(INFO_EDITOR_HEADING_CONFIGURE_COMPONENT.get(ufn));
Message heading1 = INFO_DSCFG_HEADING_PROPERTY_NAME.get();
Message heading2 = INFO_DSCFG_HEADING_PROPERTY_VALUE.get();
builder.setColumnHeadings(heading1, heading2);
builder.setColumnWidths(null, 0);
// Create an option for editing/viewing each property.
for (PropertyDefinition<?> pd : c) {
// Determine whether this property should be modifiable.
boolean isReadOnly = false;
if (pd.hasOption(PropertyOption.MONITORING)) {
isReadOnly = true;
}
if (!isCreate && pd.hasOption(PropertyOption.READ_ONLY)) {
isReadOnly = true;
}
// Create the appropriate property action.
MenuCallback<Boolean> callback;
if (pd.hasOption(PropertyOption.MULTI_VALUED)) {
if (isReadOnly) {
callback = new ReadOnlyPropertyViewer(mo, pd);
} else {
callback = new MultiValuedPropertyEditor(mo, pd);
}
} else {
if (isReadOnly) {
callback = new ReadOnlyPropertyViewer(mo, pd);
} else {
callback = new SingleValuedPropertyEditor(mo, pd);
}
}
// Create the numeric option.
Message values = getPropertyValues(pd, mo);
builder.addNumberedOption(Message.raw("%s", pd.getName()), callback,
values);
}
// Add a help option which displays a summary of the managed
// object's definition.
HelpCallback helpCallback = new ComponentHelpCallback(mo, c);
builder.addHelpOption(helpCallback);
// Add an option to apply the changes.
if (isCreate) {
builder.addCharOption(INFO_EDITOR_OPTION_FINISH_KEY.get(),
INFO_EDITOR_OPTION_FINISH_CREATE_COMPONENT.get(ufn), MenuResult
.success(true));
} else {
builder.addCharOption(INFO_EDITOR_OPTION_FINISH_KEY.get(),
INFO_EDITOR_OPTION_FINISH_MODIFY_COMPONENT.get(ufn), MenuResult
.success(true));
}
builder.setDefault(INFO_EDITOR_OPTION_FINISH_KEY.get(), MenuResult
.success(true));
// Add options for canceling and quitting.
if (app.isMenuDrivenMode()) {
builder.addCancelOption(false);
}
builder.addQuitOption();
// Run the menu - success indicates that any changes should be
// committed.
app.println();
app.println();
Menu<Boolean> menu = builder.toMenu();
MenuResult<Boolean> result = menu.run();
if (result.isSuccess()) {
if (result.getValue()) {
return MenuResult.<Void>success();
}
} else if (result.isCancel()) {
return MenuResult.cancel();
} else {
return MenuResult.quit();
}
}
}
/**
* Register the modification in the list of modifications.
* @param <T> The type of the underlying property associated with the
* modification.
* @param pd the property definition.
* @param newValues the resulting values of the property once the
* modification is applied.
* @param previousValues the values we had before the modification is applied
* (these are not necessarily the *original* values if we already have other
* modifications applied to the same property).
*/
private <T> void registerModification(PropertyDefinition<T> pd,
SortedSet<T> newValues, SortedSet<T> previousValues)
{
if (isLastChoiceReset)
{
registerResetModification(pd, previousValues);
}
else if (!newValues.equals(previousValues))
{
if (newValues.containsAll(previousValues))
{
if (newValues.size() <= 1)
{
registerSetModification(pd, newValues, previousValues);
}
else
{
registerAddModification(pd, newValues, previousValues);
}
}
else if (previousValues.containsAll(newValues))
{
registerRemoveModification(pd, newValues, previousValues);
}
else if (newValues.size() <= 1)
{
registerSetModification(pd, newValues, previousValues);
}
else
{
// Split into two operations: remove and add
SortedSet<T> removedValues = new TreeSet<T>();
removedValues.addAll(previousValues);
removedValues.removeAll(newValues);
PropertyEditorModification<T> removeMod =
PropertyEditorModification.createRemoveModification(pd,
removedValues, previousValues);
addModification(removeMod);
SortedSet<T> retainedValues = new TreeSet<T>();
retainedValues.addAll(previousValues);
retainedValues.retainAll(newValues);
SortedSet<T> addedValues = new TreeSet<T>();
addedValues.addAll(newValues);
addedValues.removeAll(retainedValues);
PropertyEditorModification<T> addMod =
PropertyEditorModification.createAddModification(pd,
addedValues, retainedValues);
addModification(addMod);
}
}
}
/**
* Register a reset modification in the list of modifications.
* @param <T> The type of the underlying property associated with the
* modification.
* @param pd the property definition.
* @param previousValues the values we had before the modification is applied
* (these are not necessarily the *original* values if we already have other
* modifications applied to the same property).
*/
private <T> void registerResetModification(PropertyDefinition<T> pd,
SortedSet<T> previousValues)
{
PropertyEditorModification<?> mod = getModification(pd);
SortedSet<T> originalValues;
if (mod != null)
{
originalValues = new TreeSet<T>(pd);
castAndAddValues(originalValues, mod.getOriginalValues(), pd);
removeModification(mod);
}
else
{
originalValues = new TreeSet<T>(previousValues);
}
addModification(PropertyEditorModification.createResetModification(pd,
originalValues));
}
/**
* Register a set modification in the list of modifications.
* @param <T> The type of the underlying property associated with the
* modification.
* @param pd the property definition.
* @param newValues the resulting values of the property once the
* modification is applied.
* @param previousValues the values we had before the modification is applied
* (these are not necessarily the *original* values if we already have other
* modifications applied to the same property).
*/
private <T> void registerSetModification(PropertyDefinition<T> pd,
SortedSet<T> newValues, SortedSet<T> previousValues)
{
PropertyEditorModification<?> mod = getModification(pd);
SortedSet<T> originalValues;
if (mod != null)
{
originalValues = new TreeSet<T>(pd);
castAndAddValues(originalValues, mod.getOriginalValues(), pd);
removeModification(mod);
}
else
{
originalValues = new TreeSet<T>(previousValues);
}
addModification(PropertyEditorModification.createSetModification(pd,
newValues, originalValues));
}
/**
* Register an add modification in the list of modifications.
* @param <T> The type of the underlying property associated with the
* modification.
* @param pd the property definition.
* @param newValues the resulting values of the property once the
* modification is applied.
* @param previousValues the values we had before the modification is applied
* (these are not necessarily the *original* values if we already have other
* modifications applied to the same property).
*/
private <T> void registerAddModification(PropertyDefinition<T> pd,
SortedSet<T> newValues, SortedSet<T> previousValues)
{
PropertyEditorModification<?> mod = getModification(pd);
PropertyEditorModification<T> newMod;
SortedSet<T> originalValues;
if (mod != null)
{
originalValues = new TreeSet<T>(pd);
castAndAddValues(originalValues, mod.getOriginalValues(), pd);
if (mod.getType() == PropertyEditorModification.Type.ADD)
{
SortedSet<T> addedValues = new TreeSet<T>(newValues);
addedValues.removeAll(originalValues);
newMod = PropertyEditorModification.createAddModification(pd,
addedValues, originalValues);
}
else
{
newMod = PropertyEditorModification.createSetModification(pd,
new TreeSet<T>(newValues), originalValues);
}
removeModification(mod);
}
else
{
originalValues = new TreeSet<T>(previousValues);
SortedSet<T> addedValues = new TreeSet<T>(newValues);
addedValues.removeAll(originalValues);
newMod = PropertyEditorModification.createAddModification(pd,
addedValues, originalValues);
}
addModification(newMod);
}
/**
* Register a remove modification in the list of modifications.
* @param <T> The type of the underlying property associated with the
* modification.
* @param pd the property definition.
* @param newValues the resulting values of the property once the
* modification is applied.
* @param previousValues the values we had before the modification is applied
* (these are not necessarily the *original* values if we already have other
* modifications applied to the same property).
*/
private <T> void registerRemoveModification(PropertyDefinition<T> pd,
SortedSet<T> newValues, SortedSet<T> previousValues)
{
PropertyEditorModification<?> mod = getModification(pd);
PropertyEditorModification<T> newMod;
SortedSet<T> originalValues;
if (mod != null)
{
originalValues = new TreeSet<T>(pd);
castAndAddValues(originalValues, mod.getOriginalValues(), pd);
if (newValues.isEmpty())
{
newMod = PropertyEditorModification.createRemoveModification(pd,
originalValues, originalValues);
}
else if (mod.getType() == PropertyEditorModification.Type.REMOVE)
{
SortedSet<T> removedValues = new TreeSet<T>(originalValues);
removedValues.removeAll(newValues);
newMod = PropertyEditorModification.createRemoveModification(pd,
removedValues, originalValues);
}
else
{
newMod = PropertyEditorModification.createSetModification(pd,
new TreeSet<T>(newValues), originalValues);
}
removeModification(mod);
}
else
{
originalValues = new TreeSet<T>(previousValues);
SortedSet<T> removedValues = new TreeSet<T>(originalValues);
removedValues.removeAll(newValues);
newMod = PropertyEditorModification.createRemoveModification(pd,
removedValues, originalValues);
}
addModification(newMod);
}
/**
* Returns the modifications that have been applied during the last call of
* the method PropertyValueEditor.edit.
* @return the modifications that have been applied during the last call of
* the method PropertyValueEditor.edit.
*/
public Collection<PropertyEditorModification<?>> getModifications()
{
return mods;
}
/**
* Clears the list of modifications.
*/
public void resetModifications()
{
mods.clear();
}
/**
* Adds a modification to the list of modifications that have been performed.
* @param <T> The type of the underlying property associated with the
* modification.
* @param mod the modification to be added.
*/
private <T> void addModification(PropertyEditorModification<T> mod)
{
mods.add(mod);
}
/**
* Removes a modification from the list of modifications that have been
* performed.
* @param <T> The type of the underlying property associated with the
* modification.
* @param mod the modification to be removed.
*/
private <T> boolean removeModification(PropertyEditorModification<T> mod)
{
return mods.remove(mod);
}
/**
* Returns the modification associated with a given property definition:
* we assume that we have only one modification per property definition (in
* the worst case we merge the modifications and generate a unique set
* modification).
* @param <T> The type of the underlying property associated with the
* modification.
* @param pd the property definition.
* @return the modification associated with the provided property definition
* and <CODE>null</CODE> if no modification could be found.
*/
private <T> PropertyEditorModification<?> getModification(
PropertyDefinition<T> pd)
{
PropertyEditorModification<?> mod = null;
for (PropertyEditorModification<?> m : mods)
{
if (pd.equals(m.getPropertyDefinition()))
{
mod = m;
break;
}
}
return mod;
}
/**
* This method is required to avoid compilation warnings. It basically adds
* the contents of a collection to another collection by explicitly casting
* its values. This is done because the method getModification() returns
* au undefined type.
* @param <T> The type of the destination values.
* @param destination the collection that we want to update.
* @param source the collection whose values we want to add (and cast) to the
* source collection.
* @param pd the PropertyDefinition we use to do the casting.
* @throws ClassCastException if an error occurs during the cast of the
* objects.
*/
private <T> void castAndAddValues(Collection<T> destination,
Collection<?> source, PropertyDefinition<T> pd) throws ClassCastException
{
for (Object o : source)
{
destination.add(pd.castValue(o));
}
}
}