Skip to content

Commit e1ae87d

Browse files
committed
Use Parsington for string parsing
1 parent 8203b52 commit e1ae87d

1 file changed

Lines changed: 78 additions & 16 deletions

File tree

src/main/java/org/scijava/convert/StringToArrayConverter.java

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
package org.scijava.convert;
3131

3232
import org.scijava.Priority;
33+
import org.scijava.parsington.ExpressionParser;
34+
import org.scijava.parsington.SyntaxTree;
3335
import org.scijava.plugin.Parameter;
3436
import org.scijava.plugin.Plugin;
3537
import org.scijava.util.Types;
@@ -38,6 +40,7 @@
3840
import java.lang.reflect.Type;
3941
import java.util.ArrayList;
4042
import java.util.List;
43+
import java.util.Optional;
4144

4245
/**
4346
* A {@link Converter} that specializes in converting {@link String}s to
@@ -47,12 +50,14 @@
4750
*
4851
* @author Gabriel Selzer
4952
*/
50-
@Plugin(type = Converter.class, priority=Priority.VERY_LOW)
53+
@Plugin(type = Converter.class, priority = Priority.VERY_LOW)
5154
public class StringToArrayConverter extends AbstractConverter<String, Object> {
5255

5356
@Parameter
5457
private ConvertService convertService;
5558

59+
private final ExpressionParser parser = new ExpressionParser();
60+
5661
@Override
5762
public boolean canConvert(final Class<?> src, final Class<?> dest) {
5863
if (src == null) return false;
@@ -61,19 +66,35 @@ public boolean canConvert(final Class<?> src, final Class<?> dest) {
6166
return saneSrc == String.class && saneDest.isArray();
6267
}
6368

69+
@Override
70+
public boolean canConvert(final Object src, final Type dest) {
71+
return canConvert(src, Types.raw(dest));
72+
}
73+
6474
@Override
6575
public boolean canConvert(final Object src, final Class<?> dest) {
76+
77+
// First, ensure the base types conform
6678
if (!canConvert(src.getClass(), dest)) return false;
67-
String srcString = (String) src;
68-
if (!(srcString.startsWith("{") && srcString.endsWith("}"))) return false;
69-
List<String> components = elements((String) src);
79+
// Then, ensure we can parse the string
80+
SyntaxTree tree;
81+
try {
82+
tree = parser.parseTree((String) src);
83+
}
84+
catch (IllegalArgumentException e) {
85+
return false;
86+
}
87+
// We can always convert empty arrays as we don't have to create Objects
88+
if (tree.count() == 0) return true;
89+
// Finally, ensure that we can convert the elements of the array.
7090
// NB this check is merely a heuristic. In the case of a heterogeneous
7191
// array, canConvert may falsely return positive, if later elements in the
7292
// string-ified array cannot be converted into Objects. We make this
7393
// compromise in the interest of speed, however, as ensuring correctness
7494
// would require a premature conversion of the entire array.
75-
return components.size() == 0 || convertService.supports(components.get(0),
76-
dest.getComponentType());
95+
Object testSrc = firstElement(tree);
96+
Class<?> testDest = unitComponentType(dest);
97+
return convertService.supports(testSrc, testDest);
7798
}
7899

79100
@Override
@@ -82,13 +103,19 @@ public Object convert(Object src, Type dest) {
82103
if (componentType == null) {
83104
throw new IllegalArgumentException(dest + " is not an array type!");
84105
}
85-
return convertToArray((String) src, componentType);
106+
try {
107+
SyntaxTree tree = parser.parseTree((String) src);
108+
return convertToArray(tree, Types.raw(componentType));
109+
}
110+
catch (IllegalArgumentException e) {
111+
return null;
112+
}
86113
}
87114

88115
@SuppressWarnings("unchecked")
89116
@Override
90117
public <T> T convert(Object src, Class<T> dest) {
91-
return (T) convert((String) src, (Type) dest);
118+
return (T) convert(src, (Type) dest);
92119
}
93120

94121
@Override
@@ -106,21 +133,55 @@ public Class<String> getInputType() {
106133
/**
107134
* Converts {@code src} into an array of component type {@code componentType}
108135
*
109-
* @param src the {@link String} to convert
136+
* @param tree the {@link String} to convert
110137
* @param componentType the component type of the output array
111138
* @return an array of {@code componentType} whose elements were created from
112139
* {@code src}
113140
*/
114-
private Object convertToArray(String src, final Type componentType) {
115-
List<String> elements = elements( src);
116-
Class<?> componentClass = Types.raw(componentType);
117-
final Object array = Array.newInstance(componentClass, elements.size());
118-
for (int i = 0; i < elements.size(); i++)
119-
Array.set(array, i, convertService.convert(elements.get(i),
120-
componentClass));
141+
private Object convertToArray(SyntaxTree tree, final Class<?> componentType) {
142+
// Create the array
143+
final Object array = Array.newInstance(componentType, tree.count());
144+
// Set each element of the array
145+
for (int i = 0; i < tree.count(); i++) {
146+
SyntaxTree subTree = tree.child(i);
147+
Object element;
148+
// Case 1: Element is an array
149+
if (componentType.isArray()) {
150+
element = convertToArray(subTree, componentType.getComponentType());
151+
}
152+
// Case 2: Element is a single object
153+
else {
154+
element = convertService.convert(subTree.token(), componentType);
155+
}
156+
Array.set(array, i, element);
157+
}
121158
return array;
122159
}
123160

161+
/**
162+
* Similar to {@link Class#getComponentType()}, but handles nested array types
163+
*
164+
* @param c the {@link Class} that may be an array class
165+
* @return the <em>unit</em> component type of {@link Class} {@code c}
166+
*/
167+
private Class<?> unitComponentType(Class<?> c) {
168+
if (!c.isArray()) return c;
169+
return unitComponentType(c.getComponentType());
170+
}
171+
172+
/**
173+
* Traverses {@code tree} to find the first element
174+
*
175+
* @param tree the {@link SyntaxTree} containing elements
176+
* @return the first {@link Object} in {@code tree}
177+
*/
178+
private Object firstElement(SyntaxTree tree) {
179+
while (tree.count() > 0) {
180+
tree = tree.child(0);
181+
}
182+
return tree.token();
183+
}
184+
124185
/**
125186
* Gets the elements of {@code src}.
126187
*
@@ -173,6 +234,7 @@ private List<String> splitByComma(String s) {
173234

174235
/**
175236
* Helper method used to filter and format the additions to {@code list}
237+
*
176238
* @param list the {@link List} to add to
177239
* @param s the {@link String} to (potentially) be added.
178240
*/

0 commit comments

Comments
 (0)