1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs2.util;
18
19 import java.lang.reflect.Array;
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Modifier;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.TreeMap;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.commons.vfs2.FileSystemConfigBuilder;
32 import org.apache.commons.vfs2.FileSystemException;
33 import org.apache.commons.vfs2.FileSystemManager;
34 import org.apache.commons.vfs2.FileSystemOptions;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 public class DelegatingFileSystemOptionsBuilder {
51
52
53
54
55 private static final class Context {
56 private final FileSystemOptions fso;
57 private final String scheme;
58 private final String name;
59 private final Object[] values;
60
61 private List<Method> configSetters;
62 private FileSystemConfigBuilder fileSystemConfigBuilder;
63
64 private Context(final FileSystemOptions fso, final String scheme, final String name, final Object[] values) {
65 this.fso = fso;
66 this.scheme = scheme;
67 this.name = name;
68 this.values = values;
69 }
70 }
71 @SuppressWarnings("unchecked")
72 private static final Class<String>[] STRING_PARAM = new Class[] {String.class};
73 private static final Map<String, Class<?>> PRIMITIVE_TO_OBJECT = new TreeMap<>();
74
75 private static final Log log = LogFactory.getLog(DelegatingFileSystemOptionsBuilder.class);
76 static {
77 PRIMITIVE_TO_OBJECT.put(Void.TYPE.getName(), Void.class);
78 PRIMITIVE_TO_OBJECT.put(Boolean.TYPE.getName(), Boolean.class);
79 PRIMITIVE_TO_OBJECT.put(Byte.TYPE.getName(), Byte.class);
80 PRIMITIVE_TO_OBJECT.put(Character.TYPE.getName(), Character.class);
81 PRIMITIVE_TO_OBJECT.put(Short.TYPE.getName(), Short.class);
82 PRIMITIVE_TO_OBJECT.put(Integer.TYPE.getName(), Integer.class);
83 PRIMITIVE_TO_OBJECT.put(Long.TYPE.getName(), Long.class);
84 PRIMITIVE_TO_OBJECT.put(Double.TYPE.getName(), Double.class);
85 PRIMITIVE_TO_OBJECT.put(Float.TYPE.getName(), Float.class);
86 }
87
88 private final FileSystemManager manager;
89
90 private final Map<String, Map<String, List<Method>>> beanMethods = new TreeMap<>();
91
92
93
94
95
96
97
98
99
100 public DelegatingFileSystemOptionsBuilder(final FileSystemManager manager) {
101 this.manager = manager;
102 }
103
104
105
106
107 private boolean convertValuesAndInvoke(final Method configSetter, final Context ctx) throws FileSystemException {
108 final Class<?>[] parameters = configSetter.getParameterTypes();
109 if (parameters.length < 2) {
110 return false;
111 }
112 if (!parameters[0].isAssignableFrom(FileSystemOptions.class)) {
113 return false;
114 }
115
116 final Class<?> valueParameter = parameters[1];
117 Class<?> type;
118 if (valueParameter.isArray()) {
119 type = valueParameter.getComponentType();
120 } else {
121 if (ctx.values.length > 1) {
122 return false;
123 }
124
125 type = valueParameter;
126 }
127
128 if (type.isPrimitive()) {
129 final Class<?> objectType = PRIMITIVE_TO_OBJECT.get(type.getName());
130 if (objectType == null) {
131 log.warn(Messages.getString("vfs.provider/config-unexpected-primitive.error", type.getName()));
132 return false;
133 }
134 type = objectType;
135 }
136
137 final Class<? extends Object> valueClass = ctx.values[0].getClass();
138 if (type.isAssignableFrom(valueClass)) {
139
140 invokeSetter(valueParameter, ctx, configSetter, ctx.values);
141 return true;
142 }
143 if (valueClass != String.class) {
144 log.warn(Messages.getString("vfs.provider/config-unexpected-value-class.error", valueClass.getName(), ctx.scheme, ctx.name));
145 return false;
146 }
147
148 final Object convertedValues = Array.newInstance(type, ctx.values.length);
149
150 Constructor<?> valueConstructor;
151 try {
152 valueConstructor = type.getConstructor(STRING_PARAM);
153 } catch (final NoSuchMethodException e) {
154 valueConstructor = null;
155 }
156 if (valueConstructor != null) {
157
158 for (int iterValues = 0; iterValues < ctx.values.length; iterValues++) {
159 try {
160 Array.set(convertedValues, iterValues, valueConstructor.newInstance(ctx.values[iterValues]));
161 } catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
162 throw new FileSystemException(e);
163 }
164 }
165
166 invokeSetter(valueParameter, ctx, configSetter, convertedValues);
167 return true;
168 }
169
170 Method valueFactory;
171 try {
172 valueFactory = type.getMethod("valueOf", STRING_PARAM);
173 if (!Modifier.isStatic(valueFactory.getModifiers())) {
174 valueFactory = null;
175 }
176 } catch (final NoSuchMethodException e) {
177 valueFactory = null;
178 }
179
180 if (valueFactory != null) {
181
182 for (int iterValues = 0; iterValues < ctx.values.length; iterValues++) {
183 try {
184 Array.set(convertedValues, iterValues, valueFactory.invoke(null, ctx.values[iterValues]));
185 } catch (final IllegalAccessException | InvocationTargetException e) {
186 throw new FileSystemException(e);
187 }
188 }
189
190 invokeSetter(valueParameter, ctx, configSetter, convertedValues);
191 return true;
192 }
193
194 return false;
195 }
196
197
198
199
200 private Map<String, List<Method>> createSchemeMethods(final String scheme) throws FileSystemException {
201 final FileSystemConfigBuilder fscb = getManager().getFileSystemConfigBuilder(scheme);
202 FileSystemException.requireNonNull(fscb, "vfs.provider/no-config-builder.error", scheme);
203
204 final Map<String, List<Method>> schemeMethods = new TreeMap<>();
205
206 final Method[] methods = fscb.getClass().getMethods();
207 for (final Method method : methods) {
208 if (!Modifier.isPublic(method.getModifiers())) {
209 continue;
210 }
211
212 final String methodName = method.getName();
213 if (!methodName.startsWith("set")) {
214
215 continue;
216 }
217
218 final String key = methodName.substring(3).toLowerCase();
219
220 final List<Method> configSetter = schemeMethods.computeIfAbsent(key, k -> new ArrayList<>(2));
221 configSetter.add(method);
222 }
223
224 return schemeMethods;
225 }
226
227
228
229
230 private boolean fillConfigSetters(final Context ctx) throws FileSystemException {
231 final Map<String, List<Method>> schemeMethods = getSchemeMethods(ctx.scheme);
232 final List<Method> configSetters = schemeMethods.get(ctx.name.toLowerCase());
233 if (configSetters == null) {
234 return false;
235 }
236
237 ctx.configSetters = configSetters;
238 return true;
239 }
240
241
242
243
244
245
246 protected FileSystemManager getManager() {
247 return manager;
248 }
249
250
251
252
253 private Map<String, List<Method>> getSchemeMethods(final String scheme) throws FileSystemException {
254 Map<String, List<Method>> schemeMethods = beanMethods.get(scheme);
255 if (schemeMethods == null) {
256 schemeMethods = createSchemeMethods(scheme);
257 beanMethods.put(scheme, schemeMethods);
258 }
259
260 return schemeMethods;
261 }
262
263
264
265
266 private void invokeSetter(final Class<?> valueParameter, final Context ctx, final Method configSetter, final Object values) throws FileSystemException {
267 final Object[] args;
268 if (valueParameter.isArray()) {
269 args = new Object[] {ctx.fso, values};
270 } else {
271 args = new Object[] {ctx.fso, Array.get(values, 0)};
272 }
273 try {
274 configSetter.invoke(ctx.fileSystemConfigBuilder, args);
275 } catch (final IllegalAccessException | InvocationTargetException e) {
276 throw new FileSystemException(e);
277 }
278 }
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293 public void setConfigClass(final FileSystemOptions fso, final String scheme, final String name,
294 final Class<?> className) throws FileSystemException, ReflectiveOperationException {
295 setConfigClasses(fso, scheme, name, new Class[] {className});
296 }
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311 public void setConfigClasses(final FileSystemOptions fso, final String scheme, final String name,
312 final Class<?>[] classNames) throws FileSystemException, ReflectiveOperationException {
313 final Object[] values = new Object[classNames.length];
314 for (int iterClassNames = 0; iterClassNames < values.length; iterClassNames++) {
315 values[iterClassNames] = classNames[iterClassNames].getConstructor().newInstance();
316 }
317
318 final Context ctx = new Context(fso, scheme, name, values);
319
320 setValues(ctx);
321 }
322
323
324
325
326
327
328
329
330
331
332 public void setConfigString(final FileSystemOptions fso, final String scheme, final String name, final String value)
333 throws FileSystemException {
334 setConfigStrings(fso, scheme, name, new String[] {value});
335 }
336
337
338
339
340
341
342
343
344
345
346 public void setConfigStrings(final FileSystemOptions fso, final String scheme, final String name,
347 final String[] values) throws FileSystemException {
348 final Context ctx = new Context(fso, scheme, name, values);
349
350 setValues(ctx);
351 }
352
353
354
355
356 private void setValues(final Context ctx) throws FileSystemException {
357
358 if (!fillConfigSetters(ctx)) {
359 throw new FileSystemException("vfs.provider/config-key-invalid.error", ctx.scheme, ctx.name);
360 }
361
362
363 ctx.fileSystemConfigBuilder = getManager().getFileSystemConfigBuilder(ctx.scheme);
364
365
366 for (final Method configSetter : ctx.configSetters) {
367 if (convertValuesAndInvoke(configSetter, ctx)) {
368 return;
369 }
370 }
371
372 throw new FileSystemException("vfs.provider/config-value-invalid.error", ctx.scheme, ctx.name, ctx.values);
373 }
374 }