View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs.util;
18  
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  import org.apache.commons.vfs.FileSystemConfigBuilder;
22  import org.apache.commons.vfs.FileSystemException;
23  import org.apache.commons.vfs.FileSystemManager;
24  import org.apache.commons.vfs.FileSystemOptions;
25  
26  import java.lang.reflect.Array;
27  import java.lang.reflect.Constructor;
28  import java.lang.reflect.InvocationTargetException;
29  import java.lang.reflect.Method;
30  import java.lang.reflect.Modifier;
31  import java.util.ArrayList;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.TreeMap;
36  
37  /***
38   * This class use reflection to set a configuration value using the fileSystemConfigBuilder
39   * associated the a scheme.<br><br>
40   * Example:<br>
41   * <pre>
42   * FileSystemOptions fso = new FileSystemOptions();
43   * DelegatingFileSystemOptionsBuilder delegate = new DelegatingFileSystemOptionsBuilder(VFS.getManager());
44   * delegate.setConfigString(fso, "sftp", "identities", "c:/tmp/test.ident");
45   * delegate.setConfigString(fso, "http", "proxyPort", "8080");
46   * delegate.setConfigClass(fso, "sftp", "userinfo", TrustEveryoneUserInfo.class);
47   * </pre>
48   *
49   * @author <a href="mailto:imario@apache.org">Mario Ivankovits</a>
50   * @version $Revision: 480428 $ $Date: 2006-11-29 07:15:24 +0100 (Mi, 29 Nov 2006) $
51   */
52  public class DelegatingFileSystemOptionsBuilder
53  {
54      private Log log = LogFactory.getLog(DelegatingFileSystemOptionsBuilder.class);
55  
56      private final static Class[] STRING_PARAM = new Class[]{String.class};
57  
58      private final FileSystemManager manager;
59  
60      private final Map beanMethods = new TreeMap();
61  
62      private final static Map primitiveToObject = new TreeMap();
63  
64      static
65      {
66          primitiveToObject.put(Void.TYPE.getName(), Void.class);
67          primitiveToObject.put(Boolean.TYPE.getName(), Boolean.class);
68          primitiveToObject.put(Byte.TYPE.getName(), Byte.class);
69          primitiveToObject.put(Character.TYPE.getName(), Character.class);
70          primitiveToObject.put(Short.TYPE.getName(), Short.class);
71          primitiveToObject.put(Integer.TYPE.getName(), Integer.class);
72          primitiveToObject.put(Long.TYPE.getName(), Long.class);
73          primitiveToObject.put(Double.TYPE.getName(), Double.class);
74          primitiveToObject.put(Float.TYPE.getName(), Float.class);
75      }
76  
77      private static class Context
78      {
79          private final FileSystemOptions fso;
80          private final String scheme;
81          private final String name;
82          private final Object[] values;
83  
84          private List configSetters;
85          private FileSystemConfigBuilder fileSystemConfigBuilder;
86  
87          private Context(final FileSystemOptions fso, final String scheme, final String name, final Object[] values)
88          {
89              this.fso = fso;
90              this.scheme = scheme;
91              this.name = name;
92              this.values = values;
93          }
94      }
95  
96      /***
97       * Constructor.<br>
98       * Pass in your fileSystemManager instance.
99       *
100      * @param manager the manager to use to get the fileSystemConfigBuilder assocated to a scheme
101      */
102     public DelegatingFileSystemOptionsBuilder(final FileSystemManager manager)
103     {
104         this.manager = manager;
105     }
106 
107     protected FileSystemManager getManager()
108     {
109         return manager;
110     }
111 
112     /***
113      * Set a single string value.
114      *
115      * @param fso    FileSystemOptions
116      * @param scheme scheme
117      * @param name   name
118      * @param value  value
119      */
120     public void setConfigString(final FileSystemOptions fso, final String scheme, final String name, final String value) throws FileSystemException
121     {
122         setConfigStrings(fso, scheme, name, new String[]{value});
123     }
124 
125     /***
126      * Set an array of string value.
127      *
128      * @param fso    FileSystemOptions
129      * @param scheme scheme
130      * @param name   name
131      * @param values values
132      */
133     public void setConfigStrings(final FileSystemOptions fso, final String scheme, final String name, final String[] values) throws FileSystemException
134     {
135         Context ctx = new Context(fso, scheme, name, values);
136 
137         setValues(ctx);
138     }
139 
140     /***
141      * Set a single class value.<br>
142      * The class has to implement a no-args constructor, else the instantiation might fail.
143      *
144      * @param fso       FileSystemOptions
145      * @param scheme    scheme
146      * @param name      name
147      * @param className className
148      */
149     public void setConfigClass(final FileSystemOptions fso, final String scheme, final String name, final Class className) throws FileSystemException, IllegalAccessException, InstantiationException
150     {
151         setConfigClasses(fso, scheme, name, new Class[]{className});
152     }
153 
154     /***
155      * Set an array of class values.<br>
156      * The class has to implement a no-args constructor, else the instantiation might fail.
157      *
158      * @param fso        FileSystemOptions
159      * @param scheme     scheme
160      * @param name       name
161      * @param classNames classNames
162      */
163     public void setConfigClasses(final FileSystemOptions fso, final String scheme, final String name, final Class[] classNames) throws FileSystemException, IllegalAccessException, InstantiationException
164     {
165         Object values[] = new Object[classNames.length];
166         for (int iterClassNames = 0; iterClassNames < values.length; iterClassNames++)
167         {
168             values[iterClassNames] = classNames[iterClassNames].newInstance();
169         }
170 
171         Context ctx = new Context(fso, scheme, name, values);
172 
173         setValues(ctx);
174     }
175 
176     /***
177      * sets the values using the informations of the given context.<br>
178      */
179     private void setValues(Context ctx) throws FileSystemException
180     {
181         // find all setter methods suitable for the given "name"
182         if (!fillConfigSetters(ctx))
183         {
184             throw new FileSystemException("vfs.provider/config-key-invalid.error", new String[]
185             {
186                 ctx.scheme,
187                 ctx.name
188             });
189         }
190 
191         // get the fileSystemConfigBuilder
192         ctx.fileSystemConfigBuilder = getManager().getFileSystemConfigBuilder(ctx.scheme);
193 
194         // try to find a setter which could accept the value
195         Iterator iterConfigSetters = ctx.configSetters.iterator();
196         while (iterConfigSetters.hasNext())
197         {
198             Method configSetter = (Method) iterConfigSetters.next();
199             if (convertValuesAndInvoke(configSetter, ctx))
200             {
201                 return;
202             }
203         }
204 
205         throw new FileSystemException("vfs.provider/config-value-invalid.error", new Object[]
206         {
207             ctx.scheme,
208             ctx.name,
209             ctx.values
210         });
211     }
212 
213     /***
214      * tries to convert the value and pass it to the given method
215      */
216     private boolean convertValuesAndInvoke(final Method configSetter, final Context ctx) throws FileSystemException
217     {
218         Class parameters[] = configSetter.getParameterTypes();
219         if (parameters.length < 2)
220         {
221             return false;
222         }
223         if (!parameters[0].isAssignableFrom(FileSystemOptions.class))
224         {
225             return false;
226         }
227 
228         Class valueParameter = parameters[1];
229         Class type;
230         if (valueParameter.isArray())
231         {
232             type = valueParameter.getComponentType();
233         }
234         else
235         {
236             if (ctx.values.length > 1)
237             {
238                 return false;
239             }
240 
241             type = valueParameter;
242         }
243 
244         if (type.isPrimitive())
245         {
246             Class objectType = (Class) primitiveToObject.get(type.getName());
247             if (objectType == null)
248             {
249                 log.warn(Messages.getString("vfs.provider/config-unexpected-primitive.error", type.getName()));
250                 return false;
251             }
252             type = objectType;
253         }
254 
255         Class valueClass = ctx.values[0].getClass();
256         if (type.isAssignableFrom(valueClass))
257         {
258             // can set value directly
259             invokeSetter(valueParameter, ctx, configSetter, ctx.values);
260             return true;
261         }
262         if (valueClass != String.class)
263         {
264             log.warn(Messages.getString("vfs.provider/config-unexpected-value-class.error", new String[]
265             {
266                 valueClass.getName(),
267                 ctx.scheme,
268                 ctx.name
269             }));
270             return false;
271         }
272 
273         Object convertedValues = java.lang.reflect.Array.newInstance(type, ctx.values.length);
274 
275         Constructor valueConstructor;
276         try
277         {
278             valueConstructor = type.getConstructor(STRING_PARAM);
279         }
280         catch (NoSuchMethodException e)
281         {
282             valueConstructor = null;
283         }
284         if (valueConstructor != null)
285         {
286             // can convert using constructor
287             for (int iterValues = 0; iterValues < ctx.values.length; iterValues++)
288             {
289                 try
290                 {
291                     Array.set(convertedValues, iterValues, valueConstructor.newInstance(new Object[]{ctx.values[iterValues]}));
292                 }
293                 catch (InstantiationException e)
294                 {
295                     throw new FileSystemException(e);
296                 }
297                 catch (IllegalAccessException e)
298                 {
299                     throw new FileSystemException(e);
300                 }
301                 catch (InvocationTargetException e)
302                 {
303                     throw new FileSystemException(e);
304                 }
305             }
306 
307             invokeSetter(valueParameter, ctx, configSetter, convertedValues);
308             return true;
309         }
310 
311         Method valueFactory;
312         try
313         {
314             valueFactory = type.getMethod("valueOf", STRING_PARAM);
315             if (!Modifier.isStatic(valueFactory.getModifiers()))
316             {
317                 valueFactory = null;
318             }
319         }
320         catch (NoSuchMethodException e)
321         {
322             valueFactory = null;
323         }
324 
325         if (valueFactory != null)
326         {
327             // can convert using factory method (valueOf)
328             for (int iterValues = 0; iterValues < ctx.values.length; iterValues++)
329             {
330                 try
331                 {
332                     Array.set(convertedValues, iterValues, valueFactory.invoke(null, new Object[]{ctx.values[iterValues]}));
333                 }
334                 catch (IllegalAccessException e)
335                 {
336                     throw new FileSystemException(e);
337                 }
338                 catch (InvocationTargetException e)
339                 {
340                     throw new FileSystemException(e);
341                 }
342             }
343 
344             invokeSetter(valueParameter, ctx, configSetter, convertedValues);
345             return true;
346         }
347 
348         return false;
349     }
350 
351     /***
352      * invokes the method with the converted values
353      */
354     private void invokeSetter(Class valueParameter, final Context ctx, final Method configSetter, final Object values)
355         throws FileSystemException
356     {
357         Object[] args;
358         if (valueParameter.isArray())
359         {
360             args = new Object[]
361             {
362                 ctx.fso,
363                 values
364             };
365         }
366         else
367         {
368             args = new Object[]
369             {
370                 ctx.fso,
371                 Array.get(values, 0)
372             };
373         }
374         try
375         {
376             configSetter.invoke(ctx.fileSystemConfigBuilder, args);
377         }
378         catch (IllegalAccessException e)
379         {
380             throw new FileSystemException(e);
381         }
382         catch (InvocationTargetException e)
383         {
384             throw new FileSystemException(e);
385         }
386     }
387 
388     /***
389      * fills all available set*() methods for the context-scheme into the context.
390      */
391     private boolean fillConfigSetters(final Context ctx)
392         throws FileSystemException
393     {
394         Map schemeMethods = getSchemeMethods(ctx.scheme);
395         List configSetters = (List) schemeMethods.get(ctx.name.toLowerCase());
396         if (configSetters == null)
397         {
398             return false;
399         }
400 
401         ctx.configSetters = configSetters;
402         return true;
403     }
404 
405     /***
406      * get (cached) list of set*() methods for the given scheme
407      */
408     private Map getSchemeMethods(final String scheme) throws FileSystemException
409     {
410         Map schemeMethods = (Map) beanMethods.get(scheme);
411         if (schemeMethods == null)
412         {
413             schemeMethods = createSchemeMethods(scheme);
414             beanMethods.put(scheme, schemeMethods);
415         }
416 
417         return schemeMethods;
418     }
419 
420     /***
421      * create the list of all set*() methods for the given scheme
422      */
423     private Map createSchemeMethods(String scheme) throws FileSystemException
424     {
425         final FileSystemConfigBuilder fscb = getManager().getFileSystemConfigBuilder(scheme);
426         if (fscb == null)
427         {
428             throw new FileSystemException("vfs.provider/no-config-builder.error", scheme);
429         }
430 
431         Map schemeMethods = new TreeMap();
432 
433         Method methods[] = fscb.getClass().getMethods();
434         for (int iterMethods = 0; iterMethods < methods.length; iterMethods++)
435         {
436             Method method = methods[iterMethods];
437             if (!Modifier.isPublic(method.getModifiers()))
438             {
439                 continue;
440             }
441 
442             String methodName = method.getName();
443             if (!methodName.startsWith("set"))
444             {
445                 // not a setter
446                 continue;
447             }
448 
449             String key = methodName.substring(3).toLowerCase();
450 
451             List configSetter = (List) schemeMethods.get(key);
452             if (configSetter == null)
453             {
454                 configSetter = new ArrayList(2);
455                 schemeMethods.put(key, configSetter);
456             }
457             configSetter.add(method);
458         }
459 
460         return schemeMethods;
461     }
462 }