aboutsummaryrefslogtreecommitdiff
path: root/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIndex.java
blob: ff8576febea05fcd9228ae91c3c864506c70f839 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package org.apache.velocity.runtime.parser.node;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.TemplateInitException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.parser.Parser;
import org.apache.velocity.util.ClassUtils;
import org.apache.velocity.util.StringUtils;
import org.apache.velocity.util.introspection.VelMethod;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 *  This node is responsible for the bracket notation at the end of
 *  a reference, e.g., $foo[1]
 */

public class ASTIndex extends SimpleNode
{
    private static final String methodName = "get";

    /**
     * Indicates if we are running in strict reference mode.
     */
    protected boolean strictRef = false;

    /**
     * @param i
     */
    public ASTIndex(int i)
    {
        super(i);
    }

    /**
     * @param p
     * @param i
     */
    public ASTIndex(Parser p, int i)
    {
        super(p, i);
    }

    /**
     * @param context
     * @param data
     * @return data
     * @throws TemplateInitException
     */
    @Override
    public Object init(InternalContextAdapter context, Object data)
        throws TemplateInitException
    {
        super.init(context, data);
        strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
        cleanupParserAndTokens();
        return data;
    }

    private final static Object[] noParams = {};
    private final static Class<?>[] noTypes = {};

    /**
     * If argument is an Integer and negative, then return (o.size() - argument).
     * Otherwise return the original argument.  We use this to calculate the true
     * index of a negative index e.g., $foo[-1]. If no size() method is found on the
     * 'o' object, then we throw an VelocityException.
     * @param argument
     * @param o
     * @param context Used to access the method cache.
     * @param node  ASTNode used for error reporting.
     * @return found object
     */
    public static Object adjMinusIndexArg(Object argument, Object o,
                               InternalContextAdapter context, SimpleNode node)
    {
      if (argument instanceof Integer && (Integer) argument < 0)
      {
          // The index value is a negative number, $foo[-1], so we want to actually
          // Index [size - value], so try and call the size method.
          VelMethod method = ClassUtils.getMethod("size", noParams, noTypes,
                             o, context, node, false);
          if (method == null)
          {
              // The object doesn't have a size method, so there is no notion of "at the end"
              throw new VelocityException(
                "A 'size()' method required for negative value "
                 + (Integer) argument + " does not exist for class '"
                 + o.getClass().getName() + "' at " + StringUtils.formatFileString(node),
                  null, node.getRuntimeServices().getLogContext().getStackTrace());
          }

          Object size = null;
          try
          {
              size = method.invoke(o, noParams);
          }
          catch (Exception e)
          {
              throw new VelocityException("Error trying to calls the 'size()' method on '"
                + o.getClass().getName() + "' at " + StringUtils.formatFileString(node), e,
                  node.getRuntimeServices().getLogContext().getStackTrace());
          }

          int sizeint = 0;
          try
          {
              sizeint = (Integer) size;
          }
          catch (ClassCastException e)
          {
              // If size() doesn't return an Integer we want to report a pretty error
              throw new VelocityException("Method 'size()' on class '"
                  + o.getClass().getName() + "' returned '" + size.getClass().getName()
                  + "' when Integer was expected at " + StringUtils.formatFileString(node),
                  null, node.getRuntimeServices().getLogContext().getStackTrace());
          }

          argument = sizeint + (Integer) argument;
      }

      // Nothing to do, return the original argument
      return argument;
    }

    /**
     * @param o
     * @param context
     * @return object value
     * @throws MethodInvocationException
     */
    @Override
    public Object execute(Object o, InternalContextAdapter context)
        throws MethodInvocationException
    {
        Object argument = jjtGetChild(0).value(context);
        // If negative, turn -1 into size - 1
        argument = adjMinusIndexArg(argument, o, context, this);
        Object [] params = {argument};
        Class<?>[] paramClasses = {argument == null ? null : argument.getClass()};

        VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses,
                                                o, context, this, strictRef);

        if (method == null) return null;

        try
        {
            /*
             *  get the returned object.  It may be null, and that is
             *  valid for something declared with a void return type.
             *  Since the caller is expecting something to be returned,
             *  as long as things are peachy, we can return an empty
             *  String so ASTReference() correctly figures out that
             *  all is well.
             */
            Object obj = method.invoke(o, params);

            if (obj == null)
            {
                if( method.getReturnType() == Void.TYPE)
                {
                    return "";
                }
            }

            return obj;
        }
        /*
         * pass through application level runtime exceptions
         */
        catch( RuntimeException e )
        {
            throw e;
        }
        catch( Exception e )
        {
            String msg = "Error invoking method 'get("
              + (argument == null ? "null" : argument.getClass().getName())
              + ")' in " + o.getClass().getName()
              + " at " + StringUtils.formatFileString(this);
            log.error(msg, e);
            throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
        }
    }
}