001    /*
002     $Id: BinaryExpression.java,v 1.8 2005/02/23 17:06:37 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
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package org.codehaus.groovy.ast.expr;
047    
048    import java.io.OutputStream;
049    import java.io.Writer;
050    import java.math.BigDecimal;
051    import java.math.BigInteger;
052    import java.util.Collection;
053    import java.util.Date;
054    import java.util.List;
055    import java.util.Map;
056    import java.util.regex.Matcher;
057    
058    import org.codehaus.groovy.ast.GroovyCodeVisitor;
059    import org.codehaus.groovy.ast.Type;
060    import org.codehaus.groovy.classgen.AsmClassGenerator;
061    import org.codehaus.groovy.syntax.Token;
062    import org.codehaus.groovy.syntax.Types;
063    import groovy.lang.GString;
064    
065    /**
066     * Represents two expressions and an operation
067     * 
068     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
069     * @version $Revision: 1.8 $
070     */
071    public class BinaryExpression extends Expression {
072        
073        private Expression leftExpression;
074        private Expression rightExpression;
075        private Token operation;
076        
077        public BinaryExpression(Expression leftExpression,
078                                Token operation,
079                                Expression rightExpression) {
080            this.leftExpression = leftExpression;
081            this.operation = operation;
082            this.rightExpression = rightExpression;
083    
084        }
085    
086        public Class getTypeClass() {
087            typeClass = resolveThisType(operation);
088            return typeClass;
089        }
090    
091        public boolean isDynamic() {
092            return false;  //To change body of implemented methods use File | Settings | File Templates.
093        }
094    
095        private Class resolveThisType(Token operation) {
096            switch (operation.getType()) {
097                case Types.EQUAL : // = assignment
098                    if (!leftExpression.isDynamic())
099                        return leftExpression.getTypeClass();
100                    else
101                        return rightExpression.getTypeClass();
102                case Types.COMPARE_IDENTICAL :
103                case Types.COMPARE_EQUAL :
104                case Types.COMPARE_NOT_EQUAL :
105                case Types.COMPARE_GREATER_THAN :
106                case Types.COMPARE_GREATER_THAN_EQUAL :
107                case Types.COMPARE_LESS_THAN :
108                case Types.COMPARE_LESS_THAN_EQUAL :
109                case Types.KEYWORD_INSTANCEOF :
110                case Types.MATCH_REGEX :
111                    return boolean.class;
112                case Types.LOGICAL_AND :
113                case Types.LOGICAL_OR :
114                    return Boolean.class;
115                case Types.COMPARE_TO :
116                    return Integer.class;
117                case Types.PLUS :
118                case Types.PLUS_EQUAL :{
119                    if (leftExpression.getTypeClass() == String.class && rightExpression.getTypeClass() == String.class) {
120                        return String.class;
121                    }
122                    else if (leftExpression.getTypeClass() == GString.class &&
123                            (rightExpression.getTypeClass() == GString.class || rightExpression.getTypeClass() == String.class)) {
124                        return GString.class;
125                    }
126                    else if (isNumber(leftExpression.getType()) && isNumber(rightExpression.getType())) {
127                        return chooseWiderNumberType(leftExpression.getType(), rightExpression.getType());
128                    }
129                    else if (leftExpression.getTypeClass() == Date.class && Number.class.isAssignableFrom(rightExpression.getTypeClass()) ) {
130                        return Date.class;
131                    }
132                    else if (leftExpression.getTypeClass() != null && Collection.class.isAssignableFrom(leftExpression.getTypeClass() )) {
133                        return List.class;
134                    }
135                    else {
136                        return null;
137                    }
138                }
139                case Types.MINUS :
140                case Types.MINUS_EQUAL :{
141                    if (leftExpression.getTypeClass() == String.class) {
142                        return String.class;
143                    } else if (leftExpression instanceof GStringExpression && isNumber(rightExpression.getType())) {
144                        return String.class;
145                    } else if (isNumber(leftExpression.getType()) && isNumber(rightExpression.getType())) {
146                        return chooseWiderNumberType(leftExpression.getType(), rightExpression.getType());
147                    }
148                    else if (leftExpression.getTypeClass() != null && List.class.isAssignableFrom(leftExpression.getTypeClass() )) {
149                        return List.class;
150                    }
151                    else if (leftExpression.getTypeClass() == Date.class && Number.class.isAssignableFrom(rightExpression.getTypeClass()) ) {
152                        return Date.class;
153                    }
154                    else {
155                        return null;
156                    }
157                }
158                case Types.MULTIPLY :
159                case Types.MULTIPLY_EQUAL : {
160                    if (leftExpression.getTypeClass() == String.class && isNumber(rightExpression.getType())) {
161                        return String.class;
162                    } else if (leftExpression instanceof GStringExpression && isNumber(rightExpression.getType())) {
163                        return String.class;
164                    } else if (isNumber(leftExpression.getType()) && isNumber(rightExpression.getType())) {
165                        return chooseWiderNumberType(leftExpression.getType(), rightExpression.getType());
166                    }
167                    else if (leftExpression.getTypeClass() != null && Collection.class.isAssignableFrom(leftExpression.getTypeClass() )) {
168                        return List.class;
169                    }
170                    else {
171                        return null;
172                    }
173                }
174    
175                case Types.DIVIDE :
176                case Types.DIVIDE_EQUAL :
177                case Types.MOD :
178                case Types.MOD_EQUAL :
179                    if (isNumber(leftExpression.getType()) && isNumber(rightExpression.getType())) {
180                        return chooseWiderNumberType(leftExpression.getType(), rightExpression.getType());
181                    }
182                    return null;
183    
184                case Types.POWER :
185                case Types.POWER_EQUAL :
186                    if (isNumber(leftExpression.getType()) && isNumber(rightExpression.getType())) {
187                        return chooseWiderNumberType(leftExpression.getType(), rightExpression.getType());
188                    }
189                    return null;
190    
191                case Types.LEFT_SHIFT :
192                    if (isNumber(leftExpression.getType()) && isNumber(rightExpression.getType())) {
193                        return leftExpression.getTypeClass();
194                    }
195                    else if (leftExpression.getTypeClass() != null && Collection.class.isAssignableFrom(leftExpression.getTypeClass() )) {
196                        return Collection.class;
197                    }
198                    else if (leftExpression.getTypeClass() != null && OutputStream.class.isAssignableFrom(leftExpression.getTypeClass())) {
199                        return Writer.class;
200                    }
201                    else if (leftExpression.getTypeClass() != null && StringBuffer.class.isAssignableFrom(leftExpression.getTypeClass())) {
202                        return Writer.class;
203                    }
204                    return null;
205                case Types.RIGHT_SHIFT :
206                case Types.RIGHT_SHIFT_UNSIGNED :
207                    if (isNumber(leftExpression.getType()) && isNumber(rightExpression.getType())) {
208                        return leftExpression.getTypeClass();
209                    }
210                    return null;
211                case Types.FIND_REGEX :
212                    return Matcher.class;
213                case Types.LEFT_SQUARE_BRACKET :
214                    Class cls = leftExpression.getTypeClass();
215                    if (cls != null) {
216                        if (cls.isArray()) {
217                            Class elemType = cls.getComponentType();
218                            //setTypeClass(elemType);
219                            return elemType;
220                        }
221                        else if (leftExpression instanceof ListExpression) {
222                            Class elemType = ((ListExpression)leftExpression).getComponentTypeClass();
223                            //setTypeClass(elemType);
224                            return elemType;
225                        }
226                        else if (leftExpression instanceof MapExpression) {
227                            return Object.class;
228                        }
229                        else if (List.class.isAssignableFrom(cls)) {
230                            return (Object.class);
231                        }
232                        else if (Map.class.isAssignableFrom(cls)) {
233                            return (Object.class);
234                        }
235                    }
236                    break;
237            }
238            return null;
239        }
240    
241        private static boolean isNumber(String type) {
242            if (type!= null) {
243                if (    type.equals("int") ||
244                        type.equals("short") ||
245                        type.equals("byte") ||
246                        type.equals("char") ||
247                        type.equals("float") ||
248                        type.equals("long") ||
249                        type.equals("double") ||
250                        type.equals("java.lang.Short") ||
251                        type.equals("java.lang.Byte") ||
252                        type.equals("java.lang.Character") ||
253                        type.equals("java.lang.Integer") ||
254                        type.equals("java.lang.Float") ||
255                        type.equals("java.lang.Long") ||
256                        type.equals("java.lang.Double") ||
257                        type.equals("java.math.BigInteger") ||
258                        type.equals("java.math.BigDecimal"))
259                {
260                    return true;
261                }
262            }
263            return false;
264        }
265    
266        private static Class getObjectClassForNumber(String type) {
267            if (type.equals("boolean") || type.equals("java.lang.Boolean")) {
268                return Boolean.class;
269            }
270            else if (type.equals("short") || type.equals("java.lang.Short")) {
271                return Short.class;
272            }
273            else if (type.equals("int") || type.equals("java.lang.Integer")) {
274                        return Integer.class;
275            }
276            else if (type.equals("char") || type.equals("java.lang.Character")) {
277                        return Integer.class;
278            }
279            else if (type.equals("long") || type.equals("java.lang.Long")) {
280                return Long.class;
281            }
282            else if (type.equals("float") || type.equals("java.lang.Float")) {
283                return Float.class;
284            }
285            else if (type.equals("double") || type.equals("java.lang.Double")) {
286                return Double.class;
287            }
288            else if (type.equals("java.math.BigInteger")) {
289                return BigInteger.class;
290            }
291            else if (type.equals("java.math.BigDecimal")) {
292                return BigDecimal.class;
293            }
294            else {
295                return null;
296            }
297        }
298    
299        private static boolean isFloatingPoint(Class cls) {
300                    return cls == Double.class || cls == Float.class;
301            }
302    
303            private static boolean isInteger(Class cls) {
304                    return cls == Integer.class || cls == Byte.class || cls == Short.class || cls == Character.class;
305            }
306    
307            private static boolean isLong(Class cls) {
308                    return cls == Long.class;
309            }
310    
311            private static boolean isBigDecimal(Class cls) {
312                    return cls == BigDecimal.class;
313            }
314    
315            private static boolean isBigInteger(Class cls) {
316                    return cls == BigInteger.class;
317            }
318    
319        private static Class chooseWiderNumberType(String lefts, String rights) {
320            Class left = getObjectClassForNumber(lefts);
321            Class right = getObjectClassForNumber(rights);
322            if (isFloatingPoint(left) || isFloatingPoint(right)) {
323                return Double.class;
324            }
325            else if (isBigDecimal(left) || isBigDecimal(right)) {
326                return BigDecimal.class;
327            }
328            else if (isBigInteger(left) || isBigInteger(right)) {
329                return BigInteger.class;
330            }
331            else if (isLong(left) || isLong(right)){
332                return Long.class;
333            }
334            return Integer.class;
335    
336            // see NumberMath for full Groovy math promotion
337        }
338        public String toString() {
339            return super.toString() +"[" + leftExpression + operation + rightExpression + "]";
340        }
341    
342        public void visit(GroovyCodeVisitor visitor) {
343            visitor.visitBinaryExpression(this);
344        }
345    
346        public Expression transformExpression(ExpressionTransformer transformer) {
347            return new BinaryExpression(transformer.transform(leftExpression), operation, transformer.transform(rightExpression));
348        }
349    
350        public Expression getLeftExpression() {
351            return leftExpression;
352        }
353    
354        public void setLeftExpression(Expression leftExpression) {
355            this.leftExpression = leftExpression;
356        }
357    
358        public void setRightExpression(Expression rightExpression) {
359            this.rightExpression = rightExpression;
360        }
361    
362        public Token getOperation() {
363            return operation;
364        }
365    
366        public Expression getRightExpression() {
367            return rightExpression;
368        }
369    
370        public String getText() {
371            if (operation.getType() == Types.LEFT_SQUARE_BRACKET) {
372                return leftExpression.getText() + "[" + rightExpression.getText() + "]";
373            }
374            return "(" + leftExpression.getText() + " " + operation.getText() + " " + rightExpression.getText() + ")";
375        }
376        
377        
378       /**
379        *  Creates an assignment expression in which the specified expression
380        *  is written into the specified variable name.   
381        */
382        
383        public static BinaryExpression newAssignmentExpression( String variable, Expression rhs ) {
384            VariableExpression lhs = new VariableExpression( variable );
385            Token         operator = Token.newPlaceholder( Types.ASSIGN );
386        
387            return new BinaryExpression( lhs, operator, rhs );
388        }
389    
390    
391        /**
392         *  Creates variable initialization expression in which the specified expression
393         *  is written into the specified variable name.   
394         */
395         
396         public static BinaryExpression newInitializationExpression( String variable, Type type, Expression rhs ) {
397            VariableExpression lhs = new VariableExpression( variable );
398         
399            if( type != null ) {
400                lhs.setType( type.getName() );
401            }
402         
403            Token operator = Token.newPlaceholder( Types.ASSIGN );
404         
405            return new BinaryExpression( lhs, operator, rhs );
406         }
407    
408        protected  void resolveType(AsmClassGenerator resolver) {
409            leftExpression.resolve(resolver);
410            rightExpression.resolve(resolver);
411            Class cls = resolveThisType(operation);
412            if (cls != null) {
413                setTypeClass(cls);
414            }
415            else {
416                 setResolveFailed(true);
417                setFailure("unknown. the right expression may have not been resolved");
418            }
419        }
420    }