package mil.nga.giat.geowave.core.cli.prefix; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import com.beust.jcommander.Parameterized; import mil.nga.giat.geowave.core.cli.annotations.PrefixParameter; /** * This class will take a collection of objects with JCommander annotations and * create a transformed set of objects with altered option prefixes, based on * the * * @PrefixParameter annotation. It also expands the capabilities of * @ParametersDelegate, allowing you to specify a collection of objects, or a * map, where the String key is prepended as a prefix to * the commands under that object. TODO: This might work * better with a Visitor pattern */ public class JCommanderPrefixTranslator { private final Queue<ParseContext> queue = new LinkedList<ParseContext>(); // These will be used to access the "field" or "method" attribute within // Parameterized, // which is a special JCommander class. If the interface changes in the // future, this // may not work anymore. private final Field paraField; private final Field paraMethod; public JCommanderPrefixTranslator() { try { // HP Fortify "Access Specifier Manipulation" // These fields are being modified by trusted code, // in a way that is not influenced by user input paraField = Parameterized.class.getDeclaredField("m_field"); paraField.setAccessible(true); paraMethod = Parameterized.class.getDeclaredField("m_method"); paraMethod.setAccessible(true); } catch (NoSuchFieldException e) { // This is a programmer error, and will only happen if another // version // of JCommander is being used. throw new RuntimeException( e); } } public void addObject( Object object ) { ParseContext pc = new ParseContext( "", object); queue.add(pc); } public JCommanderTranslationMap translate() { // This map will hold the final translations JCommanderTranslationMap transMap = new JCommanderTranslationMap(); try { while (queue.size() > 0) { ParseContext pc = queue.remove(); Object item = pc.getObject(); // This is the JCommander class used to parse the object // hierarchy for // Parameter annotations. They kept it public ... so I used it. // Otherwise, // I'd have to parse all the annotations myself. List<Parameterized> params = Parameterized.parseArg(item); // Iterate over the parameters, copying the method or field // parameters // into new parameters in 'newClass', ensuring that we maintain // annotations. for (Parameterized param : params) { Field f = (Field) paraField.get(param); Method m = (Method) paraMethod.get(param); AnnotatedElement annotatedElement = f != null ? f : m; // If this is a delegate, then process prefix parameter, add // the item // to the queue, and move on to the next field. if (param.getDelegateAnnotation() != null) { // JCommander only cares about non null fields when // processing // ParametersDelegate. Object delegateItem = param.get(item); if (delegateItem != null) { // Prefix parameter only matters for // ParametersDelegate. PrefixParameter prefixParam = annotatedElement.getAnnotation(PrefixParameter.class); String newPrefix = pc.getPrefix(); if (prefixParam != null) { if (!newPrefix.equals("")) { newPrefix += JCommanderTranslationMap.PREFIX_SEPARATOR; } newPrefix += prefixParam.prefix(); } // Is this a list type? If so then process each // object independently. if (delegateItem instanceof Collection) { Collection<?> coll = (Collection<?>) delegateItem; for (Object collItem : coll) { ParseContext newPc = new ParseContext( newPrefix, collItem); queue.add(newPc); } } // For maps, use the key as an additional prefix // specifier. else if (delegateItem instanceof Map) { Map<?, ?> mapp = (Map<?, ?>) delegateItem; for (Map.Entry<?, ?> entry : mapp.entrySet()) { String prefix = entry.getKey().toString(); Object mapItem = entry.getValue(); String convertedPrefix = newPrefix; if (!convertedPrefix.equals("")) { convertedPrefix += JCommanderTranslationMap.PREFIX_SEPARATOR; } convertedPrefix += prefix; ParseContext newPc = new ParseContext( convertedPrefix, mapItem); queue.add(newPc); } } // Normal params delegate. else { ParseContext newPc = new ParseContext( newPrefix, delegateItem); queue.add(newPc); } } } else { // TODO: In the future, if we wanted to do // @PluginParameter, this is probably // where we'd parse it, from annotatedElement. Then we'd // add it to // transMap below. // Rename the field so there are no conflicts. Name // really doesn't matter, // but it's used for translation in transMap. String newFieldName = JavassistUtils.getNextUniqueFieldName(); // Now add an entry to the translation map. transMap.addEntry( newFieldName, item, param, pc.getPrefix(), annotatedElement); } } // Iterate Parameterized } // Iterate Queue return transMap; } catch (IllegalAccessException e) { // This should never happen, but if it does, then it's a programmer // error. throw new RuntimeException( e); } } /** * This class is used to keep context of what the current prefix is during * prefix translation for JCommander. It is stored in the queue. */ private static class ParseContext { private final String prefix; private final Object object; public ParseContext( String prefix, Object object ) { this.prefix = prefix; this.object = object; } public String getPrefix() { return prefix; } public Object getObject() { return object; } } }