All Downloads are FREE. Search and download functionalities are using the official Maven repository.

aQute.lib.specinterface.SpecInterface Maven / Gradle / Ivy

The newest version!
package aQute.lib.specinterface;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import aQute.lib.converter.Converter;

/**
 * Uses an interface to provide a _specification_ of a command line. Methods
 * with no or 1 parameter (with the same parameter type as their return type)
 * are _parameter_ methods. If the return type is boolean, they are _option_
 * methods. If a method with the same name appears in the 0 and 1 parameter
 * form, the return type must be identical.
 * 

* Each method name is available in the command line as --name, and -n, where n * is the first character of the name. If the same letter is used twice, the * names are sorted and the first appearing name gets the lower case, the second * gets the upper case, and the third and later will not have a single character * identifier. *

* Single character identifiers can be merged together. I.e. -xyz will be parsed * as -x -y -z. Except for the last one, the others must be options. *

* The command line may also contains {@code key=value} pairs. These are stored * in a map and are available as _properties() if declared on the interface. *

* There are a number of build-in values that, when needed, should be declared * on the interface. * *

 * _arguments() List<String> the argument list with the parameters removed
 * _properties() Map<String,String> any key=value pairs
 * 
* * @param */ public class SpecInterface { private final static Pattern ASSIGNMENT = Pattern.compile("(\\w++)\\s*=\\s*(\\S+)\\s*"); private final T instance; private final IllegalArgumentException failure; static class O { public O(Method m) { this.name = m.getName(); } String name; List aliases = new ArrayList<>(); Type type; Method noargs; Method withDefault; boolean hasParameter; @Override public String toString() { return aliases.stream() .collect(Collectors.joining()) + (hasParameter ? type : ""); } } public SpecInterface(T convert) { this.instance = convert; this.failure = null; } public SpecInterface(IllegalArgumentException exception) { this.instance = null; this.failure = exception; } public T instance() { if (instance == null) throw this.failure; return instance; } public String failure() { if (failure == null) return null; return failure.getMessage(); } public boolean isFailure() { return failure != null; } /** * Parse the options in a command line and return an interface that provides * the options from this command line. This will parse up to (and including) * -- or an argument that does not start with - */ public static SpecInterface getOptions(Class specification, List args, File base) throws Exception { try { List arguments = new ArrayList<>(args); Map properties = new LinkedHashMap<>(); Map values = new HashMap<>(); Map options = parse(specification); int n = 0; while (n < arguments.size()) { String option = arguments.get(n); if (option.startsWith("--")) { arguments.remove(n); if (option.equals("--")) { break; } O o = options.get(option); if (o == null) { throw new IllegalArgumentException("No such option " + option); } if (o.hasParameter) { String value = arguments.remove(n); add(values, o, value); } } else if (option.startsWith("-")) { arguments.remove(n); for (int i = 1; i < option.length(); i++) { String opt = "-" + option.charAt(i); O o = options.get(opt); if (o == null) { throw new IllegalArgumentException("No such option " + opt); } if (o.hasParameter) { if (i != option.length() - 1) { throw new IllegalArgumentException( "Option " + opt + " has a value but it is not the last in a sequence " + option); } String value = arguments.remove(n); add(values, o, value); } else { values.put(o.name, true); } } } else { Matcher m = ASSIGNMENT.matcher(option); if (m.matches()) { arguments.remove(n); properties.put(m.group(1), m.group(2)); } else { n++; } } } values.put("_arguments", arguments); values.put("_properties", properties); Converter c = new Converter(); if (base != null) c.setBase(base); return new SpecInterface<>(c.convert(specification, values)); } catch (IllegalArgumentException e) { return new SpecInterface<>(e); } } /** * A common pattern is to use the {@link SpecInterface} with a parameterized * super class. This is a convenient method that extracts the first type * parameter. * * @param baseType a parameterized type * @return null or the first type parameter */ public static Class getParameterizedSuperType(Class baseType) { assert baseType != null; Type type = baseType.getGenericSuperclass(); if (type instanceof ParameterizedType ptype) { Type p1 = ptype.getActualTypeArguments()[0]; if (p1 instanceof Class ctype) { return ctype; } } return null; } /** * A common pattern is to use the {@link SpecInterface} with a parameterized * interface. This is a convenient method that extracts the first type * parameter. * * @param baseType a parameterized type * @param interfce a interface type * @return null or the first type parameter */ public static Class getParameterizedInterfaceType(Class baseType, Class interfce) { assert baseType != null; assert interfce != null; assert interfce.isInterface(); for (Type type : baseType.getGenericInterfaces()) { if (!(type instanceof ParameterizedType ptype)) continue; if (ptype.getRawType() != interfce) continue; Type p1 = ptype.getActualTypeArguments()[0]; if (p1 instanceof Class ctype) { return ctype; } } return null; } @SuppressWarnings({ "unchecked", "rawtypes" }) static void add(Map map, O o, String value) { Object v = map.get(o.name); if (v != null) { if (Converter.isMultiple(o.type)) { if (v instanceof List l) { l.add(value); } else { List l = new ArrayList<>(); l.add(v); l.add(value); map.put(o.name, l); } } else { throw new IllegalArgumentException("multiple return values for " + o + " " + v + ", " + value); } } else map.put(o.name, value); } static Map parse(Class type) { Map map = new LinkedHashMap<>(); List methods = Arrays.asList(type.getMethods()); Collections.sort(methods, (a, b) -> a.getName() .compareTo(b.getName())); for (Method m : methods) { if (m.getParameterCount() > 1) continue; O o = map.get(m.getName()); if (o == null) { o = new O(m); map.put(m.getName(), o); char c = m.getName() .charAt(0); String alias = "-" + c; O minor = map.putIfAbsent(alias, o); if (minor != null) { alias = "-" + Character.toUpperCase(c); map.putIfAbsent(alias, o); } else { alias = null; } if (alias != null) { o.aliases.add(alias); } String longAlias = "--" + m.getName(); map.put(longAlias, o); o.aliases.add(longAlias); } if (m.getParameterCount() == 0) o.noargs = m; else o.withDefault = m; if (o.type == null) { o.type = m.getGenericReturnType(); o.hasParameter = o.type != Boolean.class && o.type != boolean.class; } else { assert o.type.equals(m.getGenericReturnType()) : "two parameter/option methods have different types " + m.getName(); } } return map; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy