001    package org.apache.commons.ognl;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.util.*;
023    
024    /**
025     * Implementation of PropertyAccessor that uses numbers and dynamic subscripts as properties to index into Lists.
026     *
027     * @author Luke Blanshard (blanshlu@netscape.net)
028     * @author Drew Davidson (drew@ognl.org)
029     */
030    public class ListPropertyAccessor
031        extends ObjectPropertyAccessor
032        implements PropertyAccessor
033    {
034    
035        @Override
036        public Object getProperty( Map<String, Object> context, Object target, Object name )
037            throws OgnlException
038        {
039            List<?> list = (List<?>) target;
040    
041            if ( name instanceof String )
042            {
043                Object result;
044    
045                if ( "size".equals( name ) )
046                {
047                    result = list.size();
048                }
049                else
050                {
051                    if ( "iterator".equals( name ) )
052                    {
053                        result = list.iterator();
054                    }
055                    else
056                    {
057                        if ( "isEmpty".equals( name ) || "empty".equals( name ) )
058                        {
059                            result = list.isEmpty() ? Boolean.TRUE : Boolean.FALSE;
060                        }
061                        else
062                        {
063                            result = super.getProperty( context, target, name );
064                        }
065                    }
066                }
067    
068                return result;
069            }
070    
071            if ( name instanceof Number )
072            {
073                return list.get( ( (Number) name ).intValue() );
074            }
075    
076            if ( name instanceof DynamicSubscript )
077            {
078                int len = list.size();
079                switch ( ( (DynamicSubscript) name ).getFlag() )
080                {
081                    case DynamicSubscript.FIRST:
082                        return len > 0 ? list.get( 0 ) : null;
083                    case DynamicSubscript.MID:
084                        return len > 0 ? list.get( len / 2 ) : null;
085                    case DynamicSubscript.LAST:
086                        return len > 0 ? list.get( len - 1 ) : null;
087                    case DynamicSubscript.ALL:
088                        return new ArrayList<Object>( list );
089                    default:
090                        break;
091                }
092            }
093    
094            throw new NoSuchPropertyException( target, name );
095        }
096    
097        @Override
098        public void setProperty( Map<String, Object> context, Object target, Object name, Object value )
099            throws OgnlException
100        {
101            if ( name instanceof String && !( (String) name ).contains( "$" ) )
102            {
103                super.setProperty( context, target, name, value );
104                return;
105            }
106    
107            @SuppressWarnings( "unchecked" ) // check performed by the invoker
108                List<Object> list = (List<Object>) target;
109    
110            if ( name instanceof Number )
111            {
112                list.set( ( (Number) name ).intValue(), value );
113                return;
114            }
115    
116            if ( name instanceof DynamicSubscript )
117            {
118                int len = list.size();
119                switch ( ( (DynamicSubscript) name ).getFlag() )
120                {
121                    case DynamicSubscript.FIRST:
122                        if ( len > 0 )
123                        {
124                            list.set( 0, value );
125                        }
126                        return;
127                    case DynamicSubscript.MID:
128                        if ( len > 0 )
129                        {
130                            list.set( len / 2, value );
131                        }
132                        return;
133                    case DynamicSubscript.LAST:
134                        if ( len > 0 )
135                        {
136                            list.set( len - 1, value );
137                        }
138                        return;
139                    case DynamicSubscript.ALL:
140                        if ( !( value instanceof Collection ) )
141                        {
142                            throw new OgnlException( "Value must be a collection" );
143                        }
144                        list.clear();
145                        list.addAll( (Collection<?>) value );
146                        return;
147                    default:
148                        return;
149                }
150            }
151    
152            throw new NoSuchPropertyException( target, name );
153        }
154    
155        @Override
156        public Class<?> getPropertyClass( OgnlContext context, Object target, Object index )
157        {
158            if ( index instanceof String )
159            {
160                String key = ( (String) index ).replaceAll( "\"", "" );
161                if ( "size".equals( key ) )
162                {
163                    return int.class;
164                }
165                if ( "iterator".equals( key ) )
166                {
167                    return Iterator.class;
168                }
169                if ( "isEmpty".equals( key ) || "empty".equals( key ) )
170                {
171                    return boolean.class;
172                }
173                return super.getPropertyClass( context, target, index );
174            }
175    
176            if ( index instanceof Number )
177            {
178                return Object.class;
179            }
180    
181            return null;
182        }
183    
184        @Override
185        public String getSourceAccessor( OgnlContext context, Object target, Object index )
186        {
187            String indexStr = index.toString().replaceAll( "\"", "" );
188    
189            if ( String.class.isInstance( index ) )
190            {
191                if ( "size".equals( indexStr ) )
192                {
193                    context.setCurrentAccessor( List.class );
194                    context.setCurrentType( int.class );
195                    return ".size()";
196                }
197                if ( "iterator".equals( indexStr ) )
198                {
199                    context.setCurrentAccessor( List.class );
200                    context.setCurrentType( Iterator.class );
201                    return ".iterator()";
202                }
203                if ( "isEmpty".equals( indexStr ) || "empty".equals( indexStr ) )
204                {
205                    context.setCurrentAccessor( List.class );
206                    context.setCurrentType( boolean.class );
207                    return ".isEmpty()";
208                }
209            }
210    
211            // TODO: This feels really inefficient, must be some better way
212            // check if the index string represents a method on a custom class implementing java.util.List instead..
213            return getSourceBeanMethod( context, target, index, indexStr, false );
214        }
215    
216        @Override
217        public String getSourceSetter( OgnlContext context, Object target, Object index )
218        {
219            String indexStr = index.toString().replaceAll( "\"", "" );
220    
221            // TODO: This feels really inefficient, must be some better way
222            // check if the index string represents a method on a custom class implementing java.util.List instead..
223            /*
224             * System.out.println("Listpropertyaccessor setter using index: " + index + " and current object: " +
225             * context.getCurrentObject() + " number is current object? " +
226             * Number.class.isInstance(context.getCurrentObject()));
227             */
228    
229            return getSourceBeanMethod( context, target, index, indexStr, true );
230        }
231    
232        private String getSourceBeanMethod( OgnlContext context, Object target, Object index, String indexStr,
233                                            boolean isSetter )
234        {
235            Object currentObject = context.getCurrentObject();
236            Class<?> currentType = context.getCurrentType();
237            if ( currentObject != null && !Number.class.isInstance( currentObject ) )
238            {
239                try
240                {
241                    if ( isSetter )
242                    {
243                        if ( OgnlRuntime.getWriteMethod( target.getClass(), indexStr ) != null
244                            || !currentType.isPrimitive() )
245                        {
246                            return super.getSourceSetter( context, target, index );
247                        }
248                    }
249                    else
250                    {
251                        if ( OgnlRuntime.getReadMethod( target.getClass(), indexStr ) != null )
252                        {
253                            return super.getSourceAccessor( context, target, index );
254                        }
255                    }
256                }
257                catch ( Throwable t )
258                {
259                    throw OgnlOps.castToRuntime( t );
260                }
261            }
262    
263            /*
264             * if (String.class.isInstance(index)) { context.setCurrentAccessor(List.class); return ""; }
265             */
266    
267            context.setCurrentAccessor( List.class );
268    
269            // need to convert to primitive for list index access
270    
271            if ( !currentType.isPrimitive() && Number.class.isAssignableFrom( currentType ) )
272            {
273                indexStr += "." + OgnlRuntime.getNumericValueGetter( currentType );
274            }
275            else if ( currentObject != null && Number.class.isAssignableFrom( currentObject.getClass() )
276                && !currentType.isPrimitive() )
277            {
278                // means it needs to be cast first as well
279    
280                String toString = String.class.isInstance( index ) && currentType != Object.class ? "" : ".toString()";
281    
282                indexStr = "org.apache.commons.ognl.OgnlOps#getIntValue(" + indexStr + toString + ")";
283            }
284    
285            context.setCurrentType( Object.class );
286    
287            return isSetter ? ".set(" + indexStr + ", $3)" : ".get(" + indexStr + ")";
288        }
289    
290    }