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