package mil.nga.giat.geowave.core.cli.prefix;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IDefaultProvider;
import com.beust.jcommander.JCommander;
/**
* This special JCommander instance does two things: 1. It initializes special
* Prefixed argument objects (via addPrefixedObject) and adds them to the
* JCommanders object list before parsing 2. It overrides the sub commands that
* are added to make them instances of PrefixedJCommander 3. It lazily
* initializes child commands using an Initializer interface.
*/
public class PrefixedJCommander extends
JCommander
{
private static Logger LOGGER = LoggerFactory.getLogger(PrefixedJCommander.class);
// Allows us to override the commanders list that's being stored
// in our parent class.
private final Map<Object, JCommander> childCommanders;
// A list of objects to add to the translator before feeding
// into the internal JCommander object.
private List<Object> prefixedObjects = null;
private boolean validate = true;
private boolean allowUnknown = false;
private IDefaultProvider defaultProvider = null;
// The map used to translate the variables back and forth.
private JCommanderTranslationMap translationMap = null;
// The initializer is used before parse to allow the user
// to add additional commands/objects to this commander before
// it is used
private PrefixedJCommanderInitializer initializer = null;
/**
* Creates a new instance of this commander
*
* @param name
* - the operation name of this commander
* @param registry
* - where to lookup children
*/
@SuppressWarnings("unchecked")
public PrefixedJCommander() {
super();
try {
// HP Fortify "Access Specifier Manipulation"
// This field is being modified by trusted code,
// in a way that is not influenced by user input
Field commandsField = JCommander.class.getDeclaredField("m_commands");
commandsField.setAccessible(true);
childCommanders = (Map<Object, JCommander>) commandsField.get(this);
}
catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
// This is a programmer error, and will only happen if another
// version
// of JCommander is being used.
LOGGER.error(
"Another version of JCommander is being used",
e);
throw new RuntimeException(
e);
}
}
/**
* This function will translate the given prefixed objects into the object
* list before parsing. This is so that their descriptions will be picked
* up.
*/
private void createMap() {
if (translationMap != null) {
throw new RuntimeException(
"This PrefixedJCommander has already been used.");
}
// Initialize
if (initializer != null) {
initializer.initialize(this);
}
JCommanderPrefixTranslator translator = new JCommanderPrefixTranslator();
// And these are the input to the translator!
if (prefixedObjects != null) {
for (Object obj : prefixedObjects) {
translator.addObject(obj);
}
}
translationMap = translator.translate();
translationMap.createFacadeObjects();
for (Object obj : translationMap.getObjects()) {
addObject(obj);
}
// Copy default parameters over for parsing.
translationMap.transformToFacade();
}
@Override
public void addCommand(
String name,
Object object,
String... aliases ) {
super.addCommand(
name,
new Object(),
aliases);
// Super annoying. Can't control creation of JCommander objects, so
// just replace it.
Iterator<Entry<Object, JCommander>> iter = childCommanders.entrySet().iterator();
Entry<Object, JCommander> last = null;
while (iter.hasNext()) {
last = iter.next();
}
PrefixedJCommander comm = new PrefixedJCommander();
comm.setProgramName(
name,
aliases);
comm.setDefaultProvider(defaultProvider);
comm.setAcceptUnknownOptions(allowUnknown);
comm.setValidate(validate);
if (object != null) {
comm.addPrefixedObject(object);
}
if (last != null) {
childCommanders.put(
last.getKey(),
comm);
}
}
@Override
public void parse(
String... args ) {
createMap();
if (validate) {
super.parse(args);
}
else {
super.parseWithoutValidation(args);
}
translationMap.transformToOriginal();
}
/**
* We replace the parseWithoutValidation() command with the setValidate
* option that we apply to all children. This is because of bug #267 in
* JCommander.
*/
@Override
public void parseWithoutValidation(
String... args ) {
throw new NotImplementedException(
"Do not use this method. Use setValidate()");
}
@Override
public void setDefaultProvider(
IDefaultProvider defaultProvider ) {
super.setDefaultProvider(defaultProvider);
this.defaultProvider = defaultProvider;
}
@Override
public void setAcceptUnknownOptions(
boolean allowUnknown ) {
super.setAcceptUnknownOptions(allowUnknown);
this.allowUnknown = allowUnknown;
}
public void setValidate(
boolean validate ) {
this.validate = validate;
}
public List<Object> getPrefixedObjects() {
return prefixedObjects;
}
public void addPrefixedObject(
Object object ) {
if (this.prefixedObjects == null) {
this.prefixedObjects = new ArrayList<>();
}
this.prefixedObjects.add(object);
}
public JCommanderTranslationMap getTranslationMap() {
return translationMap;
}
public PrefixedJCommanderInitializer getInitializer() {
return initializer;
}
public void setInitializer(
PrefixedJCommanderInitializer initializer ) {
this.initializer = initializer;
}
public interface PrefixedJCommanderInitializer
{
void initialize(
PrefixedJCommander commander );
}
}