View Javadoc

1   package org.apache.velocity.util.introspection;
2   
3   /*
4    * Copyright 2006 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License")
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.lang.reflect.Array;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.util.List;
23  
24  /**
25   * Implementation of VelMethod to provide introspective "methods" for
26   * arrays that match those that would work on a fixed-size {@link List}.
27   * Currently only size(), isEmpty(), get(int), and set(int,Object) are
28   * supported.  Later, support may be added for other read-only methods
29   * such as contains(Object) or subList(int,int).  Patches are welcome! :)
30   *
31   * @author Nathan Bubna
32   * @version $Id: VelArrayMethod.java 440740 2006-09-06 15:37:44Z nbubna $
33   */
34  public class VelArrayMethod implements VelMethod
35  {
36      public static String SIZE = "size";
37      public static String IS_EMPTY = "isEmpty";
38      public static String GET = "get";
39      public static String SET = "set";
40  
41      public static boolean supports(String methodName, Object[] params)
42      {
43          // quickest way to narrow things down is to switch
44          // on the number of parameters
45          switch (params.length)
46          {
47              case 0:
48                  // then they must be calling one of these
49                  return SIZE.equals(methodName) || IS_EMPTY.equals(methodName);
50              case 1:
51                  // must be get() with a numeric param
52                  return GET.equals(methodName) && isNumeric(params[0]);
53              case 2:
54                  // must be set() with a numeric first param
55                  return SET.equals(methodName) && isNumeric(params[0]);
56              default:
57                  // it's not a supported method
58                  return false;
59          }
60      }
61  
62      protected static boolean isNumeric(Object param)
63      {
64          if (param != null && Number.class.isAssignableFrom(param.getClass()))
65          {
66              return true;
67          }
68          //TODO? do we need to check for primitive number types?
69          return false;
70      }
71  
72  
73      final Class arrayClass;
74      final String methodName;
75      final Object[] params;
76  
77      public VelArrayMethod(Class arrayClass, String methodName, Object[] params)
78      {
79          this.methodName = methodName;
80          this.params = params;
81          this.arrayClass = arrayClass;
82      }
83  
84      protected int toInt(Object param)
85      {
86          return ((Number)param).intValue();
87      }
88  
89      public Object invoke(Object array, Object[] params) throws Exception
90      {
91          // quickest way to narrow things down is to switch
92          // on the number of parameters
93          switch (params.length)
94          {
95              // 0 params is either size() or isEmpty() (maybe iterator() someday)
96              case 0:
97                  int length = Array.getLength(array);
98                  if (SIZE.equals(methodName))
99                  {
100                     return new Integer(length);
101                 }
102                 if (IS_EMPTY.equals(methodName))
103                 {
104                     return Boolean.valueOf(length == 0);
105                 }
106 
107             // 1 param currently only could mean get() with a numeric param
108             // it could mean contains(), indexOf(), etc someday
109             case 1:
110                 try
111                 {
112                     return Array.get(array, toInt(params[0]));
113                 }
114                 catch (RuntimeException re)
115                 {
116                     throw new InvocationTargetException(re);
117                 }
118 
119             // 2 params currently means set() with a numeric first param
120             // it could later mean subList(int,int) too
121             case 2:
122                 try
123                 {
124                     int index = toInt(params[0]);
125                     // get the old value to return it (like List does)
126                     Object old = Array.get(array, index);
127                     Array.set(array, index, params[1]);
128                     return old;
129                 }
130                 catch (RuntimeException re)
131                 {
132                     throw new InvocationTargetException(re);
133                 }
134 
135             default:
136                 // if supports() was checked before creating this instance
137                 // then it should not be possible to get here
138                 throw new UnsupportedOperationException('\'' + methodName +
139                                                         "' with " + params.length +
140                                                         " parameters is not a supported array method");
141         }
142     }
143 
144     public boolean isCacheable()
145     {
146         return true;
147     }
148 
149     public String getMethodName()
150     {
151         return methodName;
152     }
153 
154     public Class getReturnType()
155     {
156         if (SIZE.equals(methodName))
157         {
158             return int.class;
159         }
160         if (GET.equals(methodName) || SET.equals(methodName))
161         {
162             // should this be Object.class instead?
163             return arrayClass.getComponentType();
164         }
165         if (IS_EMPTY.equals(methodName))
166         {
167             return boolean.class;
168         }
169         // not sure what else to do here
170         return Object.class;
171     }
172 
173 }