it.unimi.dsi.lang.ObjectParser Maven / Gradle / Ivy
Show all versions of dsi-utils Show documentation
package it.unimi.dsi.lang;
/*
* DSI utilities
*
* Copyright (C) 2006-2009 Paolo Boldi and Sebastiano Vigna
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import com.martiansoftware.jsap.ParseException;
import com.martiansoftware.jsap.StringParser;
/** A parser for simple object specifications based on strings.
*
* Whenever a particular instance of a class (not a singleton) has to be specified in textual format,
* one faces the difficulty of having {@link Class#forName(String)} but no analogous method for instances. This
* class provides a method {@link #fromSpec(String, Class, String[], String[])} that will generate object instances
* starting from a specification of the form
*
* class(arg,…)
*
* The format of the specification is rather loose, to ease use on the command line: each argument may or may not
* be quote-delimited, with the the proviso that inside quotes you have the usual escape rules, whereas without quotes the
* end of the parameter is marked by the next comma or closed parenthesis, and surrounding space is trimmed. For empty constructors,
* parentheses can be omitted. Valid examples are, for instance,
*
* java.lang.Object
* java.lang.Object()
* java.lang.String(foo)
* java.lang.String("foo")
*
*
* After parsing, we search for a constructor accepting as many strings as specified arguments, or possibly
* a string varargs constructor. The second optional argument will be used to check
* that the generated object is of the correct type, and the last argument is a list of packages that
* will be prepended in turn to the specified class name. Finally, the last argument is an optional list of static factory method
* names that will be tried before resorting to constructors. Several polymorphic versions make it possible to specify
* just a subset of the arguments.
*
*
Additionally, it is possible to specify a {@linkplain #fromSpec(Object, String, Class, String[], String[]) context object}
* that will be passed to the construction or factory method used to generate the new instance. The context is class dependent, and must
* be correctly understood by the target class. In this case, the resolution process described above proceed similarly, but
* the signatures searched for contain an additional {@link Object} argument before the string arguments.
*
*
Note that this arrangement requires some collaboration from the specified class, which must provide string-based constructors.
* If additionally you plan on saving parseable representations which require more than just the class name, you are invited
* to follow the {@link #toSpec(Object)} conventions.
*
*
This class is a JSAP
* {@link StringParser}, and can be used in a JSAP parameter
* specifications to build easily objects on the command line. Several constructors make it possible
* to generate parsers that will check for type compliance, and possibly attempt to prepend package names.
*
*/
public class ObjectParser extends StringParser {
/** A marker object used to denote lack of a context. */
private final static Object NO_CONTEXT = new Object();
/** A list of package names that will be prepended to specifications, or null
. */
private final String[] packages;
/** A list of factory methods that will be used before trying constructors, or null
. */
private final String[] factoryMethod;
/** A type that will be used to check instantiated objects. */
private final Class> type;
/** The context for this parser, or null
. */
private final Object context;
/** Creates a new object parser with given control type, list of packages and factory methods.
*
* @param type a type that will be used to check instantiated objects.
* @param packages a list of package names that will be prepended to the specification, or null
.
* @param factoryMethod a list of factory methods that will be used before trying constructors, or null
.
*/
public ObjectParser( final Class> type, final String[] packages, final String[] factoryMethod ) {
this( NO_CONTEXT, type, packages, factoryMethod );
}
/** Creates a new object parser with given control type and list of packages.
*
* @param type a type that will be used to check instantiated objects.
* @param packages a list of package names that will be prepended to the specification, or null
.
*/
public ObjectParser( final Class> type, final String[] packages ) {
this( type, packages, null );
}
/** Creates a new object parser with given control type.
*
* @param type a type that will be used to check instantiated objects.
*/
public ObjectParser( final Class> type ) {
this( type, (String[])null );
}
/** Creates a new object parser. */
public ObjectParser() {
this( Object.class );
}
/** Creates a new object parser with given context, control type, list of packages and factory methods.
*
* @param context the context for this parser (will be passed on to instantiated objects)—possibly null
.
* @param type a type that will be used to check instantiated objects.
* @param packages a list of package names that will be prepended to the specification, or null
.
* @param factoryMethod a list of factory methods that will be used before trying constructors, or null
.
*/
public ObjectParser( final Object context, final Class> type, final String[] packages, final String[] factoryMethod ) {
this.context = context;
this.type = type;
this.packages = packages;
this.factoryMethod = factoryMethod;
}
/** Creates a new object parser with given context, control type and list of packages.
*
* @param context the context for this parser (will be passed on to instantiated objects)—possibly null
.
* @param type a type that will be used to check instantiated objects.
* @param packages a list of package names that will be prepended to the specification, or null
.
*/
public ObjectParser( final Object context, final Class> type, final String[] packages ) {
this( context, type, packages, null );
}
/** Creates a new object parser with given context and control type.
*
* @param context the context for this parser (will be passed on to instantiated objects)—possibly null
.
* @param type a type that will be used to check instantiated objects.
*/
public ObjectParser( final Object context, final Class> type ) {
this( context, type, null );
}
/** Creates a new object parser with given context.
* @param context the context for this parser (will be passed on to instantiated objects)—possibly null
.
*/
public ObjectParser( final Object context ) {
this( context, Object.class );
}
@Override
public Object parse( String spec ) throws ParseException {
try {
return fromSpec( context, spec, type, packages, factoryMethod );
}
catch ( Exception e ) {
throw new ParseException( e );
}
}
/** Creates a new instance from a specification.
*
* @param spec the object specification (see the {@linkplain ObjectParser class documentation}).
*/
public static Object fromSpec( String spec ) throws IllegalArgumentException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
return fromSpec( NO_CONTEXT, spec, Object.class, null, null );
}
/** Creates a new instance from a specification using a given control type.
*
* @param spec the object specification (see the {@linkplain ObjectParser class documentation}).
* @param type a type that will be used to check instantiated objects.
*/
public static S fromSpec( String spec, final Class type ) throws IllegalArgumentException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
return fromSpec( NO_CONTEXT, spec, type, null, null );
}
/** Creates a new instance from a specification using a given control type, list of packages and factory methods.
*
* @param spec the object specification (see the {@linkplain ObjectParser class documentation}).
* @param type a type that will be used to check instantiated objects.
* @param packages a list of package names that will be prepended to the specification, or null
.
*/
public static S fromSpec( String spec, final Class type, final String[] packages ) throws IllegalArgumentException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
return fromSpec( NO_CONTEXT, spec, type, packages, null );
}
/** Creates a new instance from a specification using a given control type and list of packages.
*
* @param spec the object specification (see the {@linkplain ObjectParser class documentation}).
* @param type a type that will be used to check instantiated objects.
* @param packages a list of package names that will be prepended to the specification, or null
.
* @param factoryMethod a list of factory methods that will be used before trying constructors, or null
.
*/
public static S fromSpec( String spec, final Class type, final String[] packages, final String[] factoryMethod ) throws IllegalArgumentException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
return fromSpec( NO_CONTEXT, spec, type, packages, factoryMethod );
}
/** Creates a new instance from a context and a specification.
*
* @param context a context object, or null
.
* @param spec the object specification (see the {@linkplain ObjectParser class documentation}).
*/
public static Object fromSpec( Object context, String spec ) throws IllegalArgumentException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
return fromSpec( context, spec, Object.class, null, null );
}
/** Creates a new instance from a context and a specification using a given control type.
*
* @param context a context object, or null
.
* @param spec the object specification (see the {@linkplain ObjectParser class documentation}).
* @param type a type that will be used to check instantiated objects.
*/
public static S fromSpec( Object context, String spec, final Class type ) throws IllegalArgumentException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
return fromSpec( context, spec, type, null, null );
}
/** Creates a new instance from a context and a specification using a given control type, list of packages and factory methods.
*
* @param context a context object, or null
.
* @param spec the object specification (see the {@linkplain ObjectParser class documentation}).
* @param type a type that will be used to check instantiated objects.
* @param packages a list of package names that will be prepended to the specification, or null
.
*/
public static S fromSpec( Object context, String spec, final Class type, final String[] packages ) throws IllegalArgumentException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
return fromSpec( context, spec, type, packages, null );
}
/** Creates a new instance from a context and a specification using a given control type and list of packages.
*
* @param context a context object, or null
.
* @param spec the object specification (see the {@linkplain ObjectParser class documentation}).
* @param type a type that will be used to check instantiated objects.
* @param packages a list of package names that will be prepended to the specification, or null
.
* @param factoryMethod a list of factory methods that will be used before trying constructors, or null
.
*/
@SuppressWarnings("unchecked")
public static S fromSpec( final Object context, String spec, final Class type, final String[] packages, final String[] factoryMethod ) throws IllegalArgumentException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
spec = spec.trim();
final boolean contextualised = context != NO_CONTEXT;
int endOfName = spec.indexOf( '(' );
final int length = spec.length();
if ( endOfName < 0 ) endOfName = length;
Class extends S> klass = null;
final String className = spec.substring( 0, endOfName ).trim();
try {
klass = (Class extends S>)Class.forName( className );
}
catch( ClassNotFoundException e ) {
// We try by prefixing with the given packages
if ( packages != null ) for ( String p : packages ) {
try {
klass = (Class extends S>)Class.forName( p + "." + className );
}
catch ( ClassNotFoundException niceTry ) {}
if ( klass != null ) break;
}
}
if ( klass == null ) throw new ClassNotFoundException( className );
if ( ! type.isAssignableFrom( klass ) ) throw new ClassCastException( "Class " + klass.getSimpleName() + " is not assignable to " + type );
final ObjectArrayList