-
Notifications
You must be signed in to change notification settings - Fork 17
Home
This library parses a simple string (as read form a properties file, environment variable, system property etc) into a java type. The type can be either generic (e.g. Collection<E>
, Map<K,V>
, Optional<E>
etc), non-generic (e.g. File
, Integer
, Enum
, int[]
etc) or your own custom made classes.
Typically a type-parser is used together with reflection to find out the java type in runtime with one of these methods:
java.lang.reflect.Method.getGenericParameterTypes()
java.lang.reflect.Method.getGenericReturnType()
java.lang.reflect.Field.getGenericType()
and then convert the simple string to that type.
-
java.lang.Byte
(byte
) -
java.lang.Integer
(int
) -
java.lang.Long
(long
) -
java.lang.Short
(short
) -
java.lang.Float
(float
) -
java.lang.Double
(double
) -
java.lang.Boolean
(boolean
) -
java.lang.Character
(char
) java.math.BigInteger
java.math.BigDecimal
java.net.URL
java.net.URI
java.io.File
java.nio.file.Path
-
java.lang.String
(obviously) java.lang.Class<?>
-
java.lang.Enum<?>
(Actually any Enum type) java.lang.Number
- Any class
T
that contains a single argument static factory method that returns an instance of it self, where the argument is any supported type. Example:T.anyName(Long) : T
- Any class
T
that contains a single argument constructor where its argument is of any supported type. - Any type resolved by a
java.beans.PropertyEditor
registered injava.beans.PropertyEditorManager
(Note this feature is disabled by default. )
(The type arguments E
, K
and V
below are any of the supported types above)
- Any array class of the above types. Example
File[]
,int[]
etc. - Collection subinterfaces:
-
Collection<E>
,List<E>
,Set<E>
,SortedSet<E>
,NavigableSet<E>
,BlockingDeque<E>
,BlockingQueue<E>
,Deque<E>
,Queue<E>
.
-
- Collection classes:
-
ArrayList<E>
,HashSet<E>
,LinkedHashSet<E>
,LinkedList<E>
, ,TreeSet<E>
,EnumSet<E>
,ConcurrentLinkedQueue<E>
,ConcurrentSkipListSet<E>
,CopyOnWriteArrayList<E>
,CopyOnWriteArraySet<E>
,LinkedBlockingDeque<E>
,LinkedBlockingQueue<E>
,PriorityBlockingQueue<E>
,ArrayDeque<E>
,PriorityQueue<E>
,Stack<E>
,Vector<E>
.
-
- Map subinterfaces:
-
Map<K,V>
,SortedMap<K,V>
,ConcurrentMap<K,V>
,ConcurrentNavigableMap<K,V>
,NavigableMap<K,V>
,
-
- Map classes:
-
HashMap<K,V>
,LinkedHashMap<K,V>
,TreeMap<K,V>
,ConcurrentHashMap<K,V>
,ConcurrentSkipListMap<K,V>
,WeakHashMap<K,V>
,IdentityHashMap<K,V>
,Hashtable<K,V>
.
-
java.util.Optional<E>
public static void main(String[] args) throws Throwable {
// Create a TypeParser instance with default settings.
TypeParser parser = TypeParser.newBuilder().build();
// in the most simple form, convert a string to Integer type,
// when type known at compile time.
Integer i = parser.parse("1", Integer.class);
// The string "null" will by default be parsed to a null Object
Integer i = parser.parse("null", Integer.class); // returns a null object.
// Split a comma separated string to a Set of Integers,
// when type is known at compile time.
Set<Integer> setOfInts = parser.parse("1,2,3,4", new GenericType<Set<Integer>>() {});
// or an empty Set<Integer> when input string is "null".
Set<Integer> i = parser.parse("null", new GenericType<Set<Integer>>() {});
// Split a comma separated string to a List of Booleans.
List<Boolean> listOfBooleans = parser.parse("true, false", new GenericType<List<Boolean>>() {});
// Split a comma separated string to an array of float.
float[] arrayOfFloat = parser.parse("1.3, .4, 3.56", float[].class);
// Split a comma separated string to a Map of String and File.
Map<String, File> mapOfFiles = parser.parse("f1=C:\\temp\\f1.txt,f2=C:\\temp\\f2.txt",
new GenericType<Map<String, File>>() {});
// Parse a string to an Optional<?>
Optional<Integer> optionalInteger = parser.parse("1", new GenericType<Optional<Integer>>() {});
Optional<Integer> emptyOptional = parser.parse("null", new GenericType<Optional<Integer>>() {});
Optional<List<Integer>> optionalList = parser.parse("1,2,3", new GenericType<Optional<List<Integer>>>() {});
Optional<List<Integer>> emptyOptionalList = parser.parse("null", new GenericType<Optional<List<Integer>>>() {});
// Find the parameter type in runtime with reflection and convert the input
// string to that type.
Method m = ...; // get Method object for method "m" (see method below)
Object parameter = parser.parseType("1,2,3,4", m.getGenericParameterTypes()[0]);
// Invoke method with parameter converted to a List<Integer>.
m.invoke(null, parameter);
}
static void m(List<Integer> ints) {
// do something with a list of integers
}
Latest javadoc is [here] (http://www.labelscans.com/javadoc/type-parser/latest/apidocs/).
Please create a "new issue" in the issue tracker here at github, for any communication.
Available in the Sonatype Central Repository
Maven
<dependency>
<groupId>com.github.drapostolos</groupId>
<artifactId>type-parser</artifactId>
<version>0.8.1</version>
</dependency>
Gradle
implementation 'com.github.drapostolos:type-parser:0.8.1'
(See also Releases)
Version v0.8.1 (release)
- New features, enhancements:
- Add support for
java.util.Optional<E>
#40.
- Add support for
Version v0.7.0 (release)
- New features, enhancements:
- Add support for
java.nio.file.Path
. - Code is now built with Java 8.
- Internal refactorings/code improvements using Java8 features.
- Add support for
Version v0.6.0 (release)
- New features, enhancements:
- add single argument static factory method support, regardless of method name. #29
- Added optional support for PropertyEditor #30
- Internal refactorings/code improvements. #30
- This change contains a potential (minor) breaking change.
Internally, client registered
DynamicParser
's are now executed after type lookup for regularParser
's. In other words, if a Parser matching a given type exists it will be used and noDynamicParser
's will be called.
- This change contains a potential (minor) breaking change.
Internally, client registered
Version v0.5.0 (release)
- New features, enhancements:
- Add support for java.lang.Number type. #24.
- internal optimizations/refactorings
- Bugfixes:
- Fixed issue #25 (Not possible to create type with
valueOf(Enum)
factory method) - Fixed issue #26 (Not possible to create type with constructor taking Enum argument)
- Fixed issue #25 (Not possible to create type with
Version v0.4.0 (release)
- New features, enhancements:
- Introduced
NullStringStrategy
interface according to #21. -
[Breaking Change] Changed contract for
InputPreprocessor
interface, in favor ofNullStringStrategy
above.
(the actual contract change is: prepare(...) method does not support returning null objects any more).
- Introduced
- Bugfixes:
- None.
Version v0.3.0 (release)
- New features, enhancements:
- Introduced
DynamicParser
interface, which gives clients a way to decide in runtime if a String can be parsed to any given type. -
[Breaking Change] Removed method
TypeParserBuilder.registerParserForTypesAssignableTo(Class<?>, Parser<?>)
in favor ofDynamicParser
interface above. - Introduced new
TypeParserException
which is now thrown when parsing process fails. -
[Breaking Change] renamed
NoSuchRegisteredTypeParserException
toNoSuchRegisteredParserException
- Introduced new methods available in all
*Helper
classesType getTargetType()
List<Class<T>> getParameterizedClassArguments()
Class<T> getParameterizedClassArgumentByIndex(int)
Class<T> getTargetClass()
Class<T> getRawTargetClass()
boolean isTargetTypeParameterized()
boolean isTargetTypeAssignableTo(Class<?>)
boolean isTargetTypeEqualTo(Class<?>)
boolean isTargetTypeEqualTo(GenericType<?>)
boolean isRawTargetClassAnyOf(Class<?>...)
- Added support for these Map subinterfaces:
-
ConcurrentMap
,ConcurrentNavigableMap
,NavigableMap
,SortedMap
-
- Added support for these Map classes:
-
ConcurrentHashMap
,ConcurrentSkipListMap
,Hashtable
,IdentityHashMap
,HashMap
,TreeMap
,WeakHashMap
-
- Added support for these Collection subinterfaces:
-
BlockingQueue
,BlockingDeque
,Deque
,Queue
,NavigableSet
,SortedSet
,
-
- Added support for these Collection classes:
-
ConcurrentLinkedQueue
,ConcurrentSkipListSet
,CopyOnWriteArrayList
,CopyOnWriteArraySet
,LinkedBlockingDeque
,LinkedBlockingQueue
,PriorityBlockingQueue
,ArrayDeque
,HashSet
,LinkedList
,PriorityQueue
,Stack
,TreeSet
,Vector
-
- Added support for custom made classes with a static
valueOf(...)
method, where the argument can be any supported type, not just String. - Added support for custom made classes with a private static
valueOf(...)
method. - Added support for custom made classes with a single argument constructor where the argument can be of any supported type.
- Added support for custom made classes with a private single argument constructor.
- Introduced
- Bugfixes:
- None.
Version v0.2.0 (release)
- New features, enhancements:
-
[Breaking Change] Renamed these classes/interface/methods according to issue #10
-
interface TypeParser
->interface Parser
-
class StringToTypeParser
->class TypeParser
-
StringToTypeParserBuilder
->TypeParserBuilder
-
StringToTypeParserBuilder.registerTypeParser(...)
->registerParser(...)
-
StringToTypeParserBuilder.unregisterTypeParser(...)
->unregisterParser(...)
-
- Possible to register
Parser
for all sub-classes of a super-class according to issue #7- see new method:
TypeParserBuilder.registerParserForTypesAssignableTo(Class<?>, Parser<?>)
- see new method:
- Added support for these java types
java.net.URL
java.net.URI
java.math.BigInteger
- Re-factored unit tests
- Added these new methods according to issue #11
ParserHelper.getTargetClass() : Class<T>
ParserHelper.getParameterizedClassArguments() : List<Class<T>>
ParserHelper.getParameterizedClassArgumentByIndex(int index) : Class<T>
-
[Breaking Change] Renamed these classes/interface/methods according to issue #10
- Bugfixes:
- It is not possible to parse string to
Class<Long>
#5
- It is not possible to parse string to
Version v0.1.0 (release)
- New features, enhancements:
- Initial first release
- Bugfixes:
- None
This is the way to construct a TypeParser
with default settings:
TypeParser parser = TypeParser.newBuilder().build();
The TypeParser
has a few optional configuration options that can be set as follow:
public static void main(String[] args) {
TypeParser parser = TypeParser.newBuilder()
.setInputPreprocessor(InputPreprocessor)
.registerParser(Class<?>, Parser)
.registerParser(GenericType, Parser)
.registerDynamicParser(DynamicParser)
.setKeyValueSplitStrategy(SplitStrategy)
.setNullStringStrategy(NullStringStrategy)
.setSplitStrategy(SplitStrategy)
.unregisterParser(Class<?>)
.enablePropertyEditor()
.build();
}
For additional configuration possibilities have a look at the [javadoc] (http://www.labelscans.com/javadoc/type-parser/latest/apidocs/index.html?com/github/drapostolos/typeparser/TypeParserBuilder.html) for the TypeParserBuilder
class.
When target type is known at compile time, use either one of these two methods:
TypeParser.parse(String, Class<T>)
TypeParser.parse(String, GenericType<T>)
as follow:
public enum SomeEnum {THIS, OR, THAT}
public static void main(String[] args) throws Throwable {
// Create a TypeParser instance with default settings.
TypeParser parser = TypeParser.newBuilder().build();
// Parse input strings to various types known at compile time
Integer i = parser.parse("1", Integer.class);
File f = parser.parse("C:\\some\\path", File.class);
BigDecimal bigDecimal = parser.parse("33", BigDecimal.class);
boolean b = parser.parse("true", boolean.class);
Class<?> longClass1 = parser.parse("java.lang.Long", Class.class);
SomeEnum someEnum = parser.parse("THAT", SomeEnum.class);
// NOTE parsing the string "null" will result in a null object for non-collection types
Integer i = parser.parse("null", Integer.class); // returns null object.
}
Note! When target type is of generic-type, java does not provide a good way to extract the generic type information. However, one way (the only?) of doing it is with sub-classing. You need to subclass GenericType<T>
and provide the generic type arguments as follow:
Class<?> longClass = parser.parse("java.lang.Long", new GenericType<Class<?>>() {});
Class<Long> longCls = parser.parse("java.lang.Long", new GenericType<Class<Long>>() {});
List<Integer> integers = parser.parse("1,2,3,4,5", new GenericType<List<Integer>>() {});
Set<File> files = parser.parse("C:\\some\\path1, C:\\some\\path2", new GenericType<Set<File>>() {});
Note the ending {}
since we are creating an anonymous subclass of GenericType<T>
in above example.
Note also in above examples when parsing a string to one of the java.util.Collection<E>
types (or java.util.Map<K,V>
) the input string is by default split with a comma ,
(the default behavior can be changed with a SplitStrategy
as described further below).
The typically usage of this library is to read a string from some source (.properties file, JVM arguments, xml file etc.), find out the java type during runtime (using reflection), and then convert the string to that java type using the following method:
TypeParser.parseType(String, java.lang.reflect.Type)
The below example creates an instance of a class and calls the two methods on it, but before calling the two method the parameters passed to the methods are fetched from a .properties file and converted to expected type.
Consider the following class containing two methods, which takes two arguments respectively,
import java.io.File;
import java.util.List;
public class ClassWithMethods {
public enum SomeEnum {THIS, OR, THAT}
public String m1(List<Integer> intList, File file) throws Exception {
return String.format("m1(%s, %s),", intList, file);
}
public String m2(SomeEnum someEnum, Class<?> type) {
return String.format("m2(%s, %s),", someEnum, type);
}
}
where the following args.properties
file is located in the root of the classpath.
m1.arg0 = 1,2,3,4
m1.arg1 = C:\\temp
m2.arg0 = THAT
m2.arg1 = java.lang.Long
This code creates an instance of the ClassWithMethods
and call each method
with the parameters found in the args.properties
file. The parameters are
converted to the types according to the method signature.
public class ReflectionDemo {
private static TypeParser parser = TypeParser.newBuilder().build();
public static void main(String[] args) throws Throwable {
// load a properties file named "args.properties" in root of classpath.
Properties props = new Properties();
props.load(ClassLoader.getSystemResourceAsStream("args.properties"));
// Create the instance to call methods on
ClassWithMethods o = new ClassWithMethods();
// Call each method with parameters from args.properties
for (Method m : o.getClass().getDeclaredMethods()) {
int i = 0;
List<Object> params = new ArrayList<Object>();
for (Type t : m.getGenericParameterTypes()) {
String key = m.getName() + ".arg" + i;
String value = props.getProperty(key);
params.add(parser.parseType(value, t));
i++;
}
Object result = m.invoke(o, params.toArray());
System.out.println("invoke: " + result);
}
}
}
Console output is
invoke: m2(THAT, class java.lang.Long),
invoke: m1([1, 2, 3, 4], C:\temp),
A TypeParser
can be configured to pre process the input string in a customized way, by implementing this interface
public interface InputPreprocessor {
String prepare(String input, InputPreprocessorHelper helper);
}
The prepare(String, InputPreprocessorHelper)
method accepts the input string
and returns a modified (pre processed) version of that string. NOTE that returning a null
object from the prepare(...)
method is not supported.
See InputPreprocessor
javadoc for its full contract.
In below example we strip off any trailing curly brackets appearing in the input string.
public class MyInputPreprocessor implements InputPreprocessor {
@Override
public String prepare(String input, InputPreprocessorHelper helper) {
// replaces first '{' and last '}' with "" (empty string)
return = input.replaceAll("^\\{", "").replaceAll("\\}$", "");
}
}
public static void main(String[] args) {
TypeParser parser = TypeParser.newBuilder()
.setInputPreprocessor(new MyInputPreprocessor())
.build();
List<String> intList = parser.parse("{1,2,3}", new GenericType<List<String>>() {});
System.out.println("intList: " + intList);
}
Console output is
intList: [1, 2, 3]
Have a look at the javadoc for InputPreprocessorHelper
for helper functionality useful when pre processing the input string.
If you want to parse a string to a specific type not supported by the TypeParser
(Can be a type from another library you don't have control over, or it could be your own custom class etc.), then you can register a Parser
implementation for that type, by implementing this interface:
public interface Parser<T> {
T parse(String input, ParserHelper helper);
}
and registering it like this:
TypeParser parser = TypeParser.newBuilder()
.registerParser(ThatType.class, new Parser<ThatType>() {
@Override
public ThatType parse(String input, ParserHelper helper) {
// Construct an instance of `ThatType` based on input String.
return ThatType.getInstance(input);
}
})
.build();
parser.parse("some string to", ThatType.class);
There is an alternative way of parsing strings to a type (than creating your own Parser
for it). Note! This requires you are in control of the type (i.e you can modify it). Do one of the following:
- add a static factory method named
valueOf(String)
- add a single argument constructor taking a string.
Note1! The valueOf(String)
method and the single argument constructor can be either public
or private
.
Note2! The argument doesn't necessary needs to be a String
, it can be of any type as long the TypeParser
knows how to parse it.
Note3! If a class has both a valueOf(...)' method and a single argument constructor, the
valueOf(...)` method will be called.
public static class TypeA {
private static TypeA valueOf(String input) {
// ignoring the input for the sake of this example.
return new TypeA();
}
}
public static class TypeB {
private final Long value;
// automatically converts to Long
public TypeB(Long l) {
value = l;
}
}
public static void main(String[] args) {
TypeParser parser = TypeParser.newBuilder().build();
parser.parse("some string", TypeA.class);
parser.parse("34", TypeB.class);
}
Unlike a Parser
implementation (which is statically associated to a specific type), a DynamicParser
implementation is not tied to a specific type. A DynamicParser
implementation decides in runtime whether it can parse a string to a given type or not. The DynamicParser.parse(..., ...)
method should either return the expected object, or the constant DynamicParser.TRY_NEXT
. Multiple DynamicParser
can be registered and the TypeParser
will call them one by one (in the registered order, i.e. first one registered is the first one called) until the first one found not returning DynamicParser.TRY_NEXT
.
Here is the interface:
public interface DynamicParser extends Parser<Object> {
public static final Object TRY_NEXT = new Object();
Object parse(String input, ParserHelper helper);
}
see javadoc for its complete contract.
...example coming...
User guide is Comming...