3030package org .scijava .convert ;
3131
3232import org .scijava .Priority ;
33+ import org .scijava .parsington .ExpressionParser ;
34+ import org .scijava .parsington .SyntaxTree ;
3335import org .scijava .plugin .Parameter ;
3436import org .scijava .plugin .Plugin ;
3537import org .scijava .util .Types ;
3840import java .lang .reflect .Type ;
3941import java .util .ArrayList ;
4042import java .util .List ;
43+ import java .util .Optional ;
4144
4245/**
4346 * A {@link Converter} that specializes in converting {@link String}s to
4750 *
4851 * @author Gabriel Selzer
4952 */
50- @ Plugin (type = Converter .class , priority = Priority .VERY_LOW )
53+ @ Plugin (type = Converter .class , priority = Priority .VERY_LOW )
5154public 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