001    /*
002     * $Id: MetaBeanProperty.java,v 1.7 2005/08/04 21:55:45 phk Exp $
003     *
004     * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005     *
006     * Redistribution and use of this software and associated documentation
007     * ("Software"), with or without modification, are permitted provided that the
008     * following conditions are met:
009     *  1. Redistributions of source code must retain copyright statements and
010     * notices. Redistributions must also contain a copy of this document.
011     *  2. Redistributions in binary form must reproduce the above copyright
012     * notice, this list of conditions and the following disclaimer in the
013     * documentation and/or other materials provided with the distribution.
014     *  3. The name "groovy" must not be used to endorse or promote products
015     * derived from this Software without prior written permission of The Codehaus.
016     * For written permission, please contact info@codehaus.org.
017     *  4. Products derived from this Software may not be called "groovy" nor may
018     * "groovy" appear in their names without prior written permission of The
019     * Codehaus. "groovy" is a registered trademark of The Codehaus.
020     *  5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
021     *
022     * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
023     * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
024     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
025     * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
026     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
027     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
028     * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
029     * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
030     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
031     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
032     * DAMAGE.
033     *
034     */
035    package groovy.lang;
036    
037    
038    import java.math.BigDecimal;
039    import java.math.BigInteger;
040    
041    import org.codehaus.groovy.runtime.InvokerHelper;
042    
043    /**
044     * Represents a property on a bean which may have a getter and/or a setter
045     *
046     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
047     * @author Pilho Kim
048     * @version $Revision: 1.7 $
049     */
050    public class MetaBeanProperty extends MetaProperty {
051    
052        private MetaMethod getter;
053        private MetaMethod setter;
054    
055        public MetaBeanProperty(String name, Class type, MetaMethod getter, MetaMethod setter) {
056            super(name, type);
057            this.getter = getter;
058            this.setter = setter;
059        }
060    
061        /**
062         * Get the property of the given object.
063         *
064         * @param object which to be got
065         * @return the property of the given object
066         * @throws Exception if the property could not be evaluated
067         */
068        public Object getProperty(Object object) throws Exception {
069            if (getter == null) {
070                //@todo we probably need a WriteOnlyException class
071                throw new GroovyRuntimeException("Cannot read write-only property: " + name);
072            }
073            return getter.invoke(object, MetaClass.EMPTY_ARRAY);
074        }
075    
076        /**
077         * Set the property on the given object to the new value.
078         *
079         * @param object   on which to set the property
080         * @param newValue the new value of the property
081         * @throws Exception if the property could not be set
082         */
083        public void setProperty(Object object, Object newValue) {
084            if (setter == null) {
085                throw new GroovyRuntimeException("Cannot set read-only property: " + name);
086            }
087    
088            try {
089                // we'll convert a GString to String if needed
090                if (getType() == String.class && !(newValue instanceof String)) {
091                    newValue = newValue.toString();
092                }
093                else {
094                    // Set property for primitive types
095                    newValue = coercePrimitiveValue(newValue, getType());
096                }
097    
098                setter.invoke(object, new Object[] { newValue });
099            }
100            catch (IllegalArgumentException e) {    // exception for executing as scripts
101                try {
102                    newValue = InvokerHelper.asType(newValue, getType());
103                    setter.invoke(object, new Object[] { newValue });
104                }
105                catch (Exception ex) {
106                    throw new TypeMismatchException("The property '" + toName(object.getClass()) + "." + name
107                            + "' can not refer to the value '"
108                            + newValue + "' (type " + toName(newValue.getClass())
109                            + "), because it is of the type " + toName(getType())
110                            + ". The reason is from java.lang.IllegalArgumentException.");
111                }
112            }
113            catch (ClassCastException e) {    // exception for executing as compiled classes
114                try {
115                    newValue = InvokerHelper.asType(newValue, getType());
116                    setter.invoke(object, new Object[]{newValue});
117                }
118                catch (Exception ex) {
119                    throw new TypeMismatchException("The property '" + toName(object.getClass()) + "." + name
120                            + "' can not refer to the value '"
121                            + newValue + "' (type " + toName(newValue.getClass())
122                            + "), because it is of the type " + toName(getType())
123                            + ". The reason is from java.lang.ClassCastException.");
124                }
125            }
126            catch (Exception e) {
127                throw new GroovyRuntimeException("Cannot set property: " + name +
128                        " reason: " + e.getMessage(), e);
129            }
130        }
131    
132        /**
133         * Coerce the object <code>src</code> to the target class.
134         */
135        protected static Object coercePrimitiveValue(Object src, Class target) {
136            Object newValue = src;
137    
138            if (newValue instanceof BigDecimal) {
139                if (target == java.math.BigInteger.class) {
140                    newValue = ((BigDecimal) newValue).unscaledValue();
141                }
142                else if (target == Double.class) {
143                    newValue = new Double(((BigDecimal) newValue).doubleValue());
144                }
145                else if (target == Float.class) {
146                    newValue = new Float(((BigDecimal) newValue).floatValue());
147                }
148                else if (target == Long.class) {
149                    newValue = new Long(((BigDecimal) newValue).longValue());
150                }
151                else if (target == Integer.class) {
152                    newValue = new Integer(((BigDecimal) newValue).intValue());
153                }
154                else if (target == Short.class) {
155                    newValue = new Short((short) ((BigDecimal) newValue).intValue());
156                }
157                else if (target == Byte.class) {
158                    newValue = new Byte((byte) ((BigDecimal) newValue).intValue());
159                }
160                else if (target == Character.class) {
161                    newValue = new Character((char) ((BigDecimal) newValue).intValue());
162                }
163            }
164            else if (newValue instanceof BigInteger) {
165                if (target == BigDecimal.class) {
166                    newValue = new BigDecimal((BigInteger) newValue);
167                }
168                else if (target == Double.class) {
169                    newValue = new Double(((java.math.BigInteger) newValue).doubleValue());
170                }
171                else if (target == Float.class) {
172                    newValue = new Float(((java.math.BigInteger) newValue).floatValue());
173                }
174                else if (target == Long.class) {
175                    newValue = new Long(((java.math.BigInteger) newValue).longValue());
176                }
177                else if (target == Integer.class) {
178                    newValue = new Integer(((java.math.BigInteger) newValue).intValue());
179                }
180                else if (target == Short.class) {
181                    newValue = new Short((short) ((java.math.BigInteger) newValue).intValue());
182                }
183                else if (target == Byte.class) {
184                    newValue = new Byte((byte) ((java.math.BigInteger) newValue).intValue());
185                }
186                else if (target == Character.class) {
187                    newValue = new Character((char) ((java.math.BigInteger) newValue).intValue());
188                }
189            }
190            else if (newValue instanceof java.lang.Long) {
191                if (target == Integer.class) {
192                    newValue = new Integer(((Long) newValue).intValue());
193                }
194                else if (target == Short.class) {
195                    newValue = new Short(((Long) newValue).shortValue());
196                }
197                else if (target == Byte.class) {
198                    newValue = new Byte(((Long) newValue).byteValue());
199                }
200                else if (target == Character.class) {
201                    newValue = new Character((char) ((Long) newValue).intValue());
202                }
203                else if (target == BigInteger.class) {
204                    newValue = new BigInteger("" + newValue);
205                }
206                else if (target == BigDecimal.class) {
207                    newValue = new BigDecimal("" + newValue);
208                }
209            }
210            else if (newValue instanceof java.lang.Integer) {
211                if (target == Double.class) {
212                    newValue = new Double(((Integer) newValue).intValue());
213                }
214                else if (target == Float.class) {
215                    newValue = new Float(((Integer) newValue).floatValue());
216                }
217                else if (target == Long.class) {
218                    newValue = new Long(((Integer) newValue).intValue());
219                }
220                else if (target == Short.class) {
221                    newValue = new Short(((Integer) newValue).shortValue());
222                }
223                else if (target == Byte.class) {
224                    newValue = new Byte(((Integer) newValue).byteValue());
225                }
226                else if (target == Character.class) {
227                    newValue = new Character((char) ((Integer) newValue).intValue());
228                }
229                else if (target == BigDecimal.class) {
230                    newValue = new BigDecimal("" + newValue);
231                }
232                else if (target == BigInteger.class) {
233                    newValue = new BigInteger("" + newValue);
234                }
235            }
236            else if (newValue instanceof java.lang.Short) {
237                if (target == Double.class) {
238                    newValue = new Double(((Short) newValue).shortValue());
239                }
240                else if (target == Float.class) {
241                    newValue = new Float(((Short) newValue).shortValue());
242                }
243                else if (target == Long.class) {
244                    newValue = new Long(((Short) newValue).shortValue());
245                }
246                else if (target == Integer.class) {
247                    newValue = new Integer(((Short) newValue).shortValue());
248                }
249                else if (target == Byte.class) {
250                    newValue = new Byte((byte) ((Short) newValue).shortValue());
251                }
252                else if (target == Character.class) {
253                    newValue = new Character((char) ((Short) newValue).shortValue());
254                }
255                else if (target == BigDecimal.class) {
256                    newValue = new BigDecimal("" + newValue);
257                }
258                else if (target == BigInteger.class) {
259                    newValue = new BigInteger("" + newValue);
260                }
261            }
262            else if (newValue instanceof java.lang.Byte) {
263                if (target == Double.class) {
264                    newValue = new Double(((Byte) newValue).byteValue());
265                }
266                else if (target == Float.class) {
267                    newValue = new Float(((Byte) newValue).byteValue());
268                }
269                else if (target == Long.class) {
270                    newValue = new Long(((Byte) newValue).byteValue());
271                }
272                else if (target == Integer.class) {
273                    newValue = new Integer(((Byte) newValue).byteValue());
274                }
275                else if (target == Short.class) {
276                    newValue = new Short(((Byte) newValue).byteValue());
277                }
278                else if (target == Character.class) {
279                    newValue = new Character((char) ((Byte) newValue).byteValue());
280                }
281                else if (target == BigDecimal.class) {
282                    newValue = new BigDecimal("" + newValue);
283                }
284                else if (target == BigInteger.class) {
285                    newValue = new BigInteger("" + newValue);
286                }
287            }
288            else if (newValue instanceof java.lang.Character) {
289                if (target == Double.class) {
290                    newValue = new Double(((int) ((Character) newValue).charValue() & 0xFFFF));
291                }
292                else if (target == Long.class) {
293                    newValue = new Long((long) ((Character) newValue).charValue());
294                }
295                else if (target == Integer.class) {
296                    newValue = new Integer((int) ((Character) newValue).charValue());
297                }
298                else if (target == Short.class) {
299                    newValue = new Short((short) ((Character) newValue).charValue());
300                }
301                else if (target == BigDecimal.class) {
302                    newValue = new BigDecimal("" + ((int) ((Character) newValue).charValue() & 0xFFFF));
303                }
304                else if (target == BigInteger.class) {
305                    newValue = new BigInteger("" + ((int) ((Character) newValue).charValue() & 0xFFFF));
306                }
307                else if (target == String.class) {
308                    newValue = new String("" + newValue);
309                }
310            }
311            return newValue;
312        }
313    
314        private String toName(Class c) {
315            String s = c.toString();
316            if (s.startsWith("class ") && s.length() > 6) {
317                return s.substring(6);
318            }
319            else {
320                return s;
321            }
322        }
323    
324    
325        /**
326         * Get the getter method.
327         */
328        public MetaMethod getGetter() {
329            return getter;
330        }
331    
332        /**
333         * Get the setter method.
334         */
335        public MetaMethod getSetter() {
336            return setter;
337        }
338    
339        /**
340         * This is for MetaClass to patch up the object later when looking for get*() methods.
341         */
342        void setGetter(MetaMethod getter) {
343            this.getter = getter;
344        }
345    
346        /**
347         * This is for MetaClass to patch up the object later when looking for set*() methods.
348         */
349        void setSetter(MetaMethod setter) {
350            this.setter = setter;
351        }
352    }