001    /*
002     * $Id: MetaBeanProperty.java,v 1.8 2005/10/17 08:36:21 tug 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    import org.codehaus.groovy.runtime.MetaClassHelper;
043    
044    /**
045     * Represents a property on a bean which may have a getter and/or a setter
046     *
047     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
048     * @author Pilho Kim
049     * @version $Revision: 1.8 $
050     */
051    public class MetaBeanProperty extends MetaProperty {
052    
053        private MetaMethod getter;
054        private MetaMethod setter;
055    
056        public MetaBeanProperty(String name, Class type, MetaMethod getter, MetaMethod setter) {
057            super(name, type);
058            this.getter = getter;
059            this.setter = setter;
060        }
061    
062        /**
063         * Get the property of the given object.
064         *
065         * @param object which to be got
066         * @return the property of the given object
067         * @throws Exception if the property could not be evaluated
068         */
069        public Object getProperty(Object object) throws Exception {
070            if (getter == null) {
071                //@todo we probably need a WriteOnlyException class
072                throw new GroovyRuntimeException("Cannot read write-only property: " + name);
073            }
074            return getter.invoke(object, MetaClassHelper.EMPTY_ARRAY);
075        }
076    
077        /**
078         * Set the property on the given object to the new value.
079         *
080         * @param object   on which to set the property
081         * @param newValue the new value of the property
082         * @throws Exception if the property could not be set
083         */
084        public void setProperty(Object object, Object newValue) {
085            if (setter == null) {
086                throw new GroovyRuntimeException("Cannot set read-only property: " + name);
087            }
088    
089            try {
090                // we'll convert a GString to String if needed
091                if (getType() == String.class && !(newValue instanceof String)) {
092                    newValue = newValue.toString();
093                }
094                else {
095                    // Set property for primitive types
096                    newValue = coercePrimitiveValue(newValue, getType());
097                }
098    
099                setter.invoke(object, new Object[] { newValue });
100            }
101            catch (IllegalArgumentException e) {    // exception for executing as scripts
102                try {
103                    newValue = InvokerHelper.asType(newValue, getType());
104                    setter.invoke(object, new Object[] { newValue });
105                }
106                catch (Exception ex) {
107                    throw new TypeMismatchException("The property '" + toName(object.getClass()) + "." + name
108                            + "' can not refer to the value '"
109                            + newValue + "' (type " + toName(newValue.getClass())
110                            + "), because it is of the type " + toName(getType())
111                            + ". The reason is from java.lang.IllegalArgumentException.");
112                }
113            }
114            catch (ClassCastException e) {    // exception for executing as compiled classes
115                try {
116                    newValue = InvokerHelper.asType(newValue, getType());
117                    setter.invoke(object, new Object[]{newValue});
118                }
119                catch (Exception ex) {
120                    throw new TypeMismatchException("The property '" + toName(object.getClass()) + "." + name
121                            + "' can not refer to the value '"
122                            + newValue + "' (type " + toName(newValue.getClass())
123                            + "), because it is of the type " + toName(getType())
124                            + ". The reason is from java.lang.ClassCastException.");
125                }
126            }
127            catch (Exception e) {
128                throw new GroovyRuntimeException("Cannot set property: " + name +
129                        " reason: " + e.getMessage(), e);
130            }
131        }
132    
133        /**
134         * Coerce the object <code>src</code> to the target class.
135         */
136        protected static Object coercePrimitiveValue(Object src, Class target) {
137            Object newValue = src;
138    
139            if (newValue instanceof BigDecimal) {
140                if (target == java.math.BigInteger.class) {
141                    newValue = ((BigDecimal) newValue).unscaledValue();
142                }
143                else if (target == Double.class) {
144                    newValue = new Double(((BigDecimal) newValue).doubleValue());
145                }
146                else if (target == Float.class) {
147                    newValue = new Float(((BigDecimal) newValue).floatValue());
148                }
149                else if (target == Long.class) {
150                    newValue = new Long(((BigDecimal) newValue).longValue());
151                }
152                else if (target == Integer.class) {
153                    newValue = new Integer(((BigDecimal) newValue).intValue());
154                }
155                else if (target == Short.class) {
156                    newValue = new Short((short) ((BigDecimal) newValue).intValue());
157                }
158                else if (target == Byte.class) {
159                    newValue = new Byte((byte) ((BigDecimal) newValue).intValue());
160                }
161                else if (target == Character.class) {
162                    newValue = new Character((char) ((BigDecimal) newValue).intValue());
163                }
164            }
165            else if (newValue instanceof BigInteger) {
166                if (target == BigDecimal.class) {
167                    newValue = new BigDecimal((BigInteger) newValue);
168                }
169                else if (target == Double.class) {
170                    newValue = new Double(((java.math.BigInteger) newValue).doubleValue());
171                }
172                else if (target == Float.class) {
173                    newValue = new Float(((java.math.BigInteger) newValue).floatValue());
174                }
175                else if (target == Long.class) {
176                    newValue = new Long(((java.math.BigInteger) newValue).longValue());
177                }
178                else if (target == Integer.class) {
179                    newValue = new Integer(((java.math.BigInteger) newValue).intValue());
180                }
181                else if (target == Short.class) {
182                    newValue = new Short((short) ((java.math.BigInteger) newValue).intValue());
183                }
184                else if (target == Byte.class) {
185                    newValue = new Byte((byte) ((java.math.BigInteger) newValue).intValue());
186                }
187                else if (target == Character.class) {
188                    newValue = new Character((char) ((java.math.BigInteger) newValue).intValue());
189                }
190            }
191            else if (newValue instanceof java.lang.Long) {
192                if (target == Integer.class) {
193                    newValue = new Integer(((Long) newValue).intValue());
194                }
195                else if (target == Short.class) {
196                    newValue = new Short(((Long) newValue).shortValue());
197                }
198                else if (target == Byte.class) {
199                    newValue = new Byte(((Long) newValue).byteValue());
200                }
201                else if (target == Character.class) {
202                    newValue = new Character((char) ((Long) newValue).intValue());
203                }
204                else if (target == BigInteger.class) {
205                    newValue = new BigInteger("" + newValue);
206                }
207                else if (target == BigDecimal.class) {
208                    newValue = new BigDecimal("" + newValue);
209                }
210            }
211            else if (newValue instanceof java.lang.Integer) {
212                if (target == Double.class) {
213                    newValue = new Double(((Integer) newValue).intValue());
214                }
215                else if (target == Float.class) {
216                    newValue = new Float(((Integer) newValue).floatValue());
217                }
218                else if (target == Long.class) {
219                    newValue = new Long(((Integer) newValue).intValue());
220                }
221                else if (target == Short.class) {
222                    newValue = new Short(((Integer) newValue).shortValue());
223                }
224                else if (target == Byte.class) {
225                    newValue = new Byte(((Integer) newValue).byteValue());
226                }
227                else if (target == Character.class) {
228                    newValue = new Character((char) ((Integer) newValue).intValue());
229                }
230                else if (target == BigDecimal.class) {
231                    newValue = new BigDecimal("" + newValue);
232                }
233                else if (target == BigInteger.class) {
234                    newValue = new BigInteger("" + newValue);
235                }
236            }
237            else if (newValue instanceof java.lang.Short) {
238                if (target == Double.class) {
239                    newValue = new Double(((Short) newValue).shortValue());
240                }
241                else if (target == Float.class) {
242                    newValue = new Float(((Short) newValue).shortValue());
243                }
244                else if (target == Long.class) {
245                    newValue = new Long(((Short) newValue).shortValue());
246                }
247                else if (target == Integer.class) {
248                    newValue = new Integer(((Short) newValue).shortValue());
249                }
250                else if (target == Byte.class) {
251                    newValue = new Byte((byte) ((Short) newValue).shortValue());
252                }
253                else if (target == Character.class) {
254                    newValue = new Character((char) ((Short) newValue).shortValue());
255                }
256                else if (target == BigDecimal.class) {
257                    newValue = new BigDecimal("" + newValue);
258                }
259                else if (target == BigInteger.class) {
260                    newValue = new BigInteger("" + newValue);
261                }
262            }
263            else if (newValue instanceof java.lang.Byte) {
264                if (target == Double.class) {
265                    newValue = new Double(((Byte) newValue).byteValue());
266                }
267                else if (target == Float.class) {
268                    newValue = new Float(((Byte) newValue).byteValue());
269                }
270                else if (target == Long.class) {
271                    newValue = new Long(((Byte) newValue).byteValue());
272                }
273                else if (target == Integer.class) {
274                    newValue = new Integer(((Byte) newValue).byteValue());
275                }
276                else if (target == Short.class) {
277                    newValue = new Short(((Byte) newValue).byteValue());
278                }
279                else if (target == Character.class) {
280                    newValue = new Character((char) ((Byte) newValue).byteValue());
281                }
282                else if (target == BigDecimal.class) {
283                    newValue = new BigDecimal("" + newValue);
284                }
285                else if (target == BigInteger.class) {
286                    newValue = new BigInteger("" + newValue);
287                }
288            }
289            else if (newValue instanceof java.lang.Character) {
290                if (target == Double.class) {
291                    newValue = new Double(((int) ((Character) newValue).charValue() & 0xFFFF));
292                }
293                else if (target == Long.class) {
294                    newValue = new Long((long) ((Character) newValue).charValue());
295                }
296                else if (target == Integer.class) {
297                    newValue = new Integer((int) ((Character) newValue).charValue());
298                }
299                else if (target == Short.class) {
300                    newValue = new Short((short) ((Character) newValue).charValue());
301                }
302                else if (target == BigDecimal.class) {
303                    newValue = new BigDecimal("" + ((int) ((Character) newValue).charValue() & 0xFFFF));
304                }
305                else if (target == BigInteger.class) {
306                    newValue = new BigInteger("" + ((int) ((Character) newValue).charValue() & 0xFFFF));
307                }
308                else if (target == String.class) {
309                    newValue = new String("" + newValue);
310                }
311            }
312            return newValue;
313        }
314    
315        private String toName(Class c) {
316            String s = c.toString();
317            if (s.startsWith("class ") && s.length() > 6) {
318                return s.substring(6);
319            }
320            else {
321                return s;
322            }
323        }
324    
325    
326        /**
327         * Get the getter method.
328         */
329        public MetaMethod getGetter() {
330            return getter;
331        }
332    
333        /**
334         * Get the setter method.
335         */
336        public MetaMethod getSetter() {
337            return setter;
338        }
339    
340        /**
341         * This is for MetaClass to patch up the object later when looking for get*() methods.
342         */
343        void setGetter(MetaMethod getter) {
344            this.getter = getter;
345        }
346    
347        /**
348         * This is for MetaClass to patch up the object later when looking for set*() methods.
349         */
350        void setSetter(MetaMethod setter) {
351            this.setter = setter;
352        }
353    }