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 org.apache.commons.ognl.enhance.ExpressionCompiler;
023    import org.apache.commons.ognl.enhance.UnsupportedCompilationException;
024    
025    import java.beans.IndexedPropertyDescriptor;
026    import java.beans.PropertyDescriptor;
027    import java.lang.reflect.Method;
028    import java.util.Iterator;
029    
030    /**
031     * $Id: ASTProperty.java 1197445 2011-11-04 09:13:17Z mcucchiara $
032     *
033     * @author Luke Blanshard (blanshlu@netscape.net)
034     * @author Drew Davidson (drew@ognl.org)
035     */
036    public class ASTProperty
037        extends SimpleNode
038        implements NodeType
039    {
040        private boolean indexedAccess = false;
041    
042        private Class getterClass;
043    
044        private Class setterClass;
045    
046        public ASTProperty( int id )
047        {
048            super( id );
049        }
050    
051        public void setIndexedAccess( boolean value )
052        {
053            indexedAccess = value;
054        }
055    
056        /**
057         * Returns true if this property is itself an index reference.
058         *
059         * @return Returns true if this property is itself an index reference.
060         */
061        public boolean isIndexedAccess()
062        {
063            return indexedAccess;
064        }
065    
066        /**
067         * Returns true if this property is described by an IndexedPropertyDescriptor and that if followed by an index
068         * specifier it will call the index get/set methods rather than go through property accessors.
069         *
070         * @param context The context
071         * @param source  The object source
072         * @return true, if this property is described by an IndexedPropertyDescriptor
073         * @throws OgnlException if an error occurs
074         */
075        public int getIndexedPropertyType( OgnlContext context, Object source )
076            throws OgnlException
077        {
078            Class type = context.getCurrentType();
079            Class prevType = context.getPreviousType();
080            try
081            {
082                if ( !isIndexedAccess() )
083                {
084                    Object property = getProperty( context, source );
085    
086                    if ( property instanceof String )
087                    {
088                        return OgnlRuntime.getIndexedPropertyType( context, ( source == null )
089                            ? null
090                            : OgnlRuntime.getCompiler( context ).getInterfaceClass( source.getClass() ),
091                                                                   (String) property );
092                    }
093                }
094    
095                return OgnlRuntime.INDEXED_PROPERTY_NONE;
096            }
097            finally
098            {
099                context.setCurrentObject( source );
100                context.setCurrentType( type );
101                context.setPreviousType( prevType );
102            }
103        }
104    
105        public Object getProperty( OgnlContext context, Object source )
106            throws OgnlException
107        {
108            return children[0].getValue( context, context.getRoot() );
109        }
110    
111        protected Object getValueBody( OgnlContext context, Object source )
112            throws OgnlException
113        {
114            Object property = getProperty( context, source );
115    
116            Object result = OgnlRuntime.getProperty( context, source, property );
117    
118            if ( result == null )
119            {
120                result =
121                    OgnlRuntime.getNullHandler( OgnlRuntime.getTargetClass( source ) ).nullPropertyValue( context, source,
122                                                                                                          property );
123            }
124    
125            return result;
126        }
127    
128        protected void setValueBody( OgnlContext context, Object target, Object value )
129            throws OgnlException
130        {
131            OgnlRuntime.setProperty( context, target, getProperty( context, target ), value );
132        }
133    
134        public boolean isNodeSimpleProperty( OgnlContext context )
135            throws OgnlException
136        {
137            return ( children != null ) && ( children.length == 1 ) && ( (SimpleNode) children[0] ).isConstant( context );
138        }
139    
140        public Class getGetterClass()
141        {
142            return getterClass;
143        }
144    
145        public Class getSetterClass()
146        {
147            return setterClass;
148        }
149    
150        public String toGetSourceString( OgnlContext context, Object target )
151        {
152            if ( context.getCurrentObject() == null )
153            {
154                throw new UnsupportedCompilationException( "Current target is null." );
155            }
156            String result = "";
157            Method m = null;
158    
159            try
160            {
161                /*
162                 * System.out.println("astproperty is indexed? : " + isIndexedAccess() + " child: " +
163                 * _children[0].getClass().getName() + " target: " + target.getClass().getName() + " current object: " +
164                 * context.getCurrentObject().getClass().getName());
165                 */
166    
167                Node child = children[0];
168                if ( isIndexedAccess() )
169                {
170                    Object value = child.getValue( context, context.getRoot() );
171    
172                    if ( value == null || DynamicSubscript.class.isAssignableFrom( value.getClass() ) )
173                    {
174                        throw new UnsupportedCompilationException(
175                            "Value passed as indexed property was null or not supported." );
176                    }
177                    // Get root cast string if the child is a type that needs it (like a nested ASTProperty)
178    
179                    String srcString = getSourceString( context, child );
180    
181                    if ( context.get( "_indexedMethod" ) != null )
182                    {
183                        m = (Method) context.remove( "_indexedMethod" );
184                        getterClass = m.getReturnType();
185    
186                        Object indexedValue = OgnlRuntime.callMethod( context, target, m.getName(), new Object[]{ value } );
187    
188                        context.setCurrentType( getterClass );
189                        context.setCurrentObject( indexedValue );
190                        context.setCurrentAccessor(
191                            OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
192    
193                        return "." + m.getName() + "(" + srcString + ")";
194                    }
195                    else
196                    {
197                        PropertyAccessor propertyAccessor = OgnlRuntime.getPropertyAccessor( target.getClass() );
198    
199                        // System.out.println("child value : " + _children[0].getValue(context, context.getCurrentObject())
200                        // + " using propaccessor " + p.getClass().getName()
201                        // + " and srcString " + srcString + " on target: " + target);
202    
203                        Object currentObject = context.getCurrentObject();
204                        if ( ASTConst.class.isInstance( child ) && Number.class.isInstance( currentObject ) )
205                        {
206                            context.setCurrentType( OgnlRuntime.getPrimitiveWrapperClass( currentObject.getClass() ) );
207                        }
208                        Object indexValue = propertyAccessor.getProperty( context, target, value );
209                        result = propertyAccessor.getSourceAccessor( context, target, srcString );
210                        getterClass = context.getCurrentType();
211                        context.setCurrentObject( indexValue );
212    
213                        return result;
214                    }
215                }
216    
217                String name = ( (ASTConst) child ).getValue().toString();
218    
219                target = getTarget( context, target, name );
220    
221                PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor( context.getCurrentObject().getClass(), name );
222    
223                if ( pd != null && pd.getReadMethod() != null && !context.getMemberAccess().isAccessible( context,
224                                                                                                          context.getCurrentObject(),
225                                                                                                          pd.getReadMethod(),
226                                                                                                          name ) )
227                {
228                    throw new UnsupportedCompilationException( "Member access forbidden for property " + name + " on class "
229                                                                   + context.getCurrentObject().getClass() );
230                }
231    
232                if ( this.getIndexedPropertyType( context, context.getCurrentObject() ) > 0 && pd != null )
233                {
234                    // if an indexed method accessor need to use special property descriptors to find methods
235    
236                    if ( pd instanceof IndexedPropertyDescriptor )
237                    {
238                        m = ( (IndexedPropertyDescriptor) pd ).getIndexedReadMethod();
239                    }
240                    else
241                    {
242                        if ( pd instanceof ObjectIndexedPropertyDescriptor )
243                        {
244                            m = ( (ObjectIndexedPropertyDescriptor) pd ).getIndexedReadMethod();
245                        }
246                        else
247                        {
248                            throw new OgnlException( "property '" + name + "' is not an indexed property" );
249                        }
250                    }
251    
252                    if ( parent == null )
253                    {
254                        // the above pd will be the wrong result sometimes, such as methods like getValue(int) vs String[]
255                        // getValue()
256                        m = OgnlRuntime.getReadMethod( context.getCurrentObject().getClass(), name );
257    
258                        result = m.getName() + "()";
259                        getterClass = m.getReturnType();
260                    }
261                    else
262                    {
263                        context.put( "_indexedMethod", m );
264                    }
265                }
266                else
267                {
268    
269                    /*
270                     * System.out.println("astproperty trying to get " + name + " on object target: " +
271                     * context.getCurrentObject().getClass().getName() + " current type " + context.getCurrentType() +
272                     * " current accessor " + context.getCurrentAccessor() + " prev type " + context.getPreviousType() +
273                     * " prev accessor " + context.getPreviousAccessor());
274                     */
275    
276                    PropertyAccessor pa = OgnlRuntime.getPropertyAccessor( context.getCurrentObject().getClass() );
277    
278                    if ( context.getCurrentObject().getClass().isArray() )
279                    {
280                        if ( pd == null )
281                        {
282                            pd = OgnlRuntime.getProperty( context.getCurrentObject().getClass(), name );
283    
284                            if ( pd != null && pd.getReadMethod() != null )
285                            {
286                                m = pd.getReadMethod();
287                                result = pd.getName();
288                            }
289                            else
290                            {
291                                getterClass = int.class;
292                                context.setCurrentAccessor( context.getCurrentObject().getClass() );
293                                context.setCurrentType( int.class );
294                                result = "." + name;
295                            }
296                        }
297                    }
298                    else
299                    {
300                        if ( pd != null && pd.getReadMethod() != null )
301                        {
302                            m = pd.getReadMethod();
303                            result = "." + m.getName() + "()";
304                        }
305                        else if ( pa != null )
306                        {
307                            Object currObj = context.getCurrentObject();
308                            Class currType = context.getCurrentType();
309                            Class prevType = context.getPreviousType();
310    
311                            String srcString = child.toGetSourceString( context, context.getRoot() );
312    
313                            if ( ASTConst.class.isInstance( child ) && String.class.isInstance(
314                                context.getCurrentObject() ) )
315                            {
316                                srcString = "\"" + srcString + "\"";
317                            }
318                            context.setCurrentObject( currObj );
319                            context.setCurrentType( currType );
320                            context.setPreviousType( prevType );
321    
322                            result = pa.getSourceAccessor( context, context.getCurrentObject(), srcString );
323    
324                            getterClass = context.getCurrentType();
325                        }
326                    }
327                }
328    
329            }
330            catch ( Throwable t )
331            {
332                throw OgnlOps.castToRuntime( t );
333            }
334    
335            // set known property types for NodeType interface when possible
336    
337            if ( m != null )
338            {
339                getterClass = m.getReturnType();
340    
341                context.setCurrentType( m.getReturnType() );
342                context.setCurrentAccessor(
343                    OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
344            }
345    
346            context.setCurrentObject( target );
347    
348            return result;
349        }
350    
351        Object getTarget( OgnlContext context, Object target, String name )
352            throws OgnlException
353        {
354            Class<?> clazz = context.getCurrentObject().getClass();
355            if ( !Iterator.class.isAssignableFrom( clazz ) || ( Iterator.class.isAssignableFrom( clazz ) && !name.contains(
356                "next" ) ) )
357            {
358                Object currObj = target;
359    
360                try
361                {
362                    target = getValue( context, context.getCurrentObject() );
363                }
364                catch ( NoSuchPropertyException e )
365                {
366                    try
367                    {
368                        target = getValue( context, context.getRoot() );
369                    }
370                    catch ( NoSuchPropertyException ex )
371                    {
372                        // ignore
373                    }
374                }
375                finally
376                {
377                    context.setCurrentObject( currObj );
378                }
379            }
380            return target;
381        }
382    
383        Method getIndexedWriteMethod( PropertyDescriptor pd )
384        {
385            if ( IndexedPropertyDescriptor.class.isInstance( pd ) )
386            {
387                return ( (IndexedPropertyDescriptor) pd ).getIndexedWriteMethod();
388            }
389            else if ( ObjectIndexedPropertyDescriptor.class.isInstance( pd ) )
390            {
391                return ( (ObjectIndexedPropertyDescriptor) pd ).getIndexedWriteMethod();
392            }
393    
394            return null;
395        }
396    
397        public String toSetSourceString( OgnlContext context, Object target )
398        {
399            String result = "";
400            Method m = null;
401    
402            if ( context.getCurrentObject() == null )
403            {
404                throw new UnsupportedCompilationException( "Current target is null." );
405            }
406            /*
407             * System.out.println("astproperty(setter) is indexed? : " + isIndexedAccess() + " child: " +
408             * _children[0].getClass().getName() + " target: " + target.getClass().getName() + " children length: " +
409             * _children.length);
410             */
411    
412            try
413            {
414    
415                Node child = children[0];
416                if ( isIndexedAccess() )
417                {
418                    Object value = child.getValue( context, context.getRoot() );
419    
420                    if ( value == null )
421                    {
422                        throw new UnsupportedCompilationException(
423                            "Value passed as indexed property is null, can't enhance statement to bytecode." );
424                    }
425    
426                    String srcString = getSourceString( context, child );
427    
428                    // System.out.println("astproperty setter using indexed value " + value + " and srcString: " +
429                    // srcString);
430    
431                    if ( context.get( "_indexedMethod" ) != null )
432                    {
433                        m = (Method) context.remove( "_indexedMethod" );
434                        PropertyDescriptor pd = (PropertyDescriptor) context.remove( "_indexedDescriptor" );
435    
436                        boolean lastChild = lastChild( context );
437                        if ( lastChild )
438                        {
439                            m = getIndexedWriteMethod( pd );
440    
441                            if ( m == null )
442                            {
443                                throw new UnsupportedCompilationException(
444                                    "Indexed property has no corresponding write method." );
445                            }
446                        }
447    
448                        setterClass = m.getParameterTypes()[0];
449    
450                        Object indexedValue = null;
451                        if ( !lastChild )
452                        {
453                            indexedValue = OgnlRuntime.callMethod( context, target, m.getName(), new Object[]{ value } );
454                        }
455                        context.setCurrentType( setterClass );
456                        context.setCurrentAccessor(
457                            OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
458    
459                        if ( !lastChild )
460                        {
461                            context.setCurrentObject( indexedValue );
462                            return "." + m.getName() + "(" + srcString + ")";
463                        }
464                        else
465                        {
466                            return "." + m.getName() + "(" + srcString + ", $3)";
467                        }
468                    }
469                    else
470                    {
471                        PropertyAccessor propertyAccessor = OgnlRuntime.getPropertyAccessor( target.getClass() );
472    
473                        Object currentObject = context.getCurrentObject();
474                        if ( ASTConst.class.isInstance( child ) && Number.class.isInstance( currentObject ) )
475                        {
476                            context.setCurrentType( OgnlRuntime.getPrimitiveWrapperClass( currentObject.getClass() ) );
477                        }
478                        Object indexValue = propertyAccessor.getProperty( context, target, value );
479                        result = lastChild( context )
480                            ? propertyAccessor.getSourceSetter( context, target, srcString )
481                            : propertyAccessor.getSourceAccessor( context, target, srcString );
482    
483                        /*
484                         * System.out.println("ASTProperty using propertyaccessor and isLastChild? " + lastChild(context) +
485                         * " generated source of: " + result + " using accessor class: " + p.getClass().getName());
486                         */
487    
488                        // result = p.getSourceAccessor(context, target, srcString);
489                        getterClass = context.getCurrentType();
490                        context.setCurrentObject( indexValue );
491    
492                        /*
493                         * PropertyAccessor p = OgnlRuntime.getPropertyAccessor(target.getClass()); if
494                         * (ASTConst.class.isInstance(_children[0]) && Number.class.isInstance(context.getCurrentObject()))
495                         * {
496                         * context.setCurrentType(OgnlRuntime.getPrimitiveWrapperClass(context.getCurrentObject().getClass(
497                         * ))); } result = p.getSourceSetter(context, target, srcString); context.setCurrentObject(value);
498                         * context.setCurrentType(getterClass);
499                         */
500                        return result;
501                    }
502                }
503    
504                String name = ( (ASTConst) child ).getValue().toString();
505    
506                // System.out.println(" astprop(setter) : trying to set " + name + " on object target " +
507                // context.getCurrentObject().getClass().getName());
508    
509                target = getTarget( context, target, name );
510    
511                PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor(
512                    OgnlRuntime.getCompiler( context ).getInterfaceClass( context.getCurrentObject().getClass() ), name );
513    
514                if ( pd != null )
515                {
516                    Method pdMethod = lastChild( context ) ? pd.getWriteMethod() : pd.getReadMethod();
517    
518                    if ( pdMethod != null && !context.getMemberAccess().isAccessible( context, context.getCurrentObject(),
519                                                                                      pdMethod, name ) )
520                    {
521                        throw new UnsupportedCompilationException(
522                            "Member access forbidden for property " + name + " on class "
523                                + context.getCurrentObject().getClass() );
524                    }
525                }
526    
527                if ( pd != null && this.getIndexedPropertyType( context, context.getCurrentObject() ) > 0 )
528                {
529                    // if an indexed method accessor need to use special property descriptors to find methods
530    
531                    if ( pd instanceof IndexedPropertyDescriptor )
532                    {
533                        IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
534                        m = lastChild( context ) ? ipd.getIndexedWriteMethod() : ipd.getIndexedReadMethod();
535                    }
536                    else
537                    {
538                        if ( pd instanceof ObjectIndexedPropertyDescriptor )
539                        {
540                            ObjectIndexedPropertyDescriptor opd = (ObjectIndexedPropertyDescriptor) pd;
541    
542                            m = lastChild( context ) ? opd.getIndexedWriteMethod() : opd.getIndexedReadMethod();
543                        }
544                        else
545                        {
546                            throw new OgnlException( "property '" + name + "' is not an indexed property" );
547                        }
548                    }
549    
550                    if ( parent == null )
551                    {
552                        // the above pd will be the wrong result sometimes, such as methods like getValue(int) vs String[]
553                        // getValue()
554    
555                        m = OgnlRuntime.getWriteMethod( context.getCurrentObject().getClass(), name );
556                        Class parm = m.getParameterTypes()[0];
557                        String cast = parm.isArray() ? ExpressionCompiler.getCastString( parm ) : parm.getName();
558    
559                        result = m.getName() + "((" + cast + ")$3)";
560                        setterClass = parm;
561                    }
562                    else
563                    {
564                        context.put( "_indexedMethod", m );
565                        context.put( "_indexedDescriptor", pd );
566                    }
567    
568                }
569                else
570                {
571                    PropertyAccessor pa = OgnlRuntime.getPropertyAccessor( context.getCurrentObject().getClass() );
572    
573                    /*
574                     * System.out.println("astproperty trying to set " + name + " on object target: " +
575                     * context.getCurrentObject().getClass().getName() + " using propertyaccessor type: " + pa);
576                     */
577    
578                    if ( target != null )
579                    {
580                        setterClass = target.getClass();
581                    }
582                    if ( parent != null && pd != null && pa == null )
583                    {
584                        m = pd.getReadMethod();
585                        result = m.getName() + "()";
586                    }
587                    else
588                    {
589                        if ( context.getCurrentObject().getClass().isArray() )
590                        {
591                            result = "";
592                        }
593                        else if ( pa != null )
594                        {
595                            Object currObj = context.getCurrentObject();
596                            // Class currType = context.getCurrentType();
597                            // Class prevType = context.getPreviousType();
598    
599                            String srcString = child.toGetSourceString( context, context.getRoot() );
600    
601                            if ( ASTConst.class.isInstance( child ) && String.class.isInstance(
602                                context.getCurrentObject() ) )
603                            {
604                                srcString = "\"" + srcString + "\"";
605                            }
606    
607                            context.setCurrentObject( currObj );
608                            // context.setCurrentType(currType);
609                            // context.setPreviousType(prevType);
610    
611                            if ( !lastChild( context ) )
612                            {
613                                result = pa.getSourceAccessor( context, context.getCurrentObject(), srcString );
614                            }
615                            else
616                            {
617                                result = pa.getSourceSetter( context, context.getCurrentObject(), srcString );
618                            }
619    
620                            getterClass = context.getCurrentType();
621                        }
622                    }
623                }
624    
625            }
626            catch ( Throwable t )
627            {
628                throw OgnlOps.castToRuntime( t );
629            }
630    
631            context.setCurrentObject( target );
632    
633            if ( m != null )
634            {
635                context.setCurrentType( m.getReturnType() );
636                context.setCurrentAccessor(
637                    OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
638            }
639    
640            return result;
641        }
642    
643        public <R, P> R accept( NodeVisitor<? extends R, ? super P> visitor, P data )
644            throws OgnlException
645        {
646            return visitor.visit( this, data );
647        }
648    
649        private static String getSourceString( OgnlContext context, Node child )
650        {
651            String srcString = child.toGetSourceString( context, context.getRoot() );
652            srcString = ExpressionCompiler.getRootExpression( child, context.getRoot(), context ) + srcString;
653    
654            if ( ASTChain.class.isInstance( child ) )
655            {
656                String cast = (String) context.remove( ExpressionCompiler.PRE_CAST );
657                if ( cast != null )
658                {
659                    srcString = cast + srcString;
660                }
661            }
662    
663            if ( ASTConst.class.isInstance( child ) && String.class.isInstance( context.getCurrentObject() ) )
664            {
665                srcString = "\"" + srcString + "\"";
666            }
667            // System.out.println("indexed getting with child srcString: " + srcString + " value class: " +
668            // value.getClass() + " and child: " + _children[0].getClass());
669            return srcString;
670        }
671    
672    }