All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.java.hints.ArithmeticUtilities Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.java.hints;

import com.sun.source.tree.AssertTree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.modules.java.hints.errors.Utilities;
import static com.sun.source.tree.Tree.Kind.*;

/**
 * Constant expression evaluator. 
 * Evaluates the constant expression encoded by tree at a certain TreePath. If the tree does not produce a constant expression,
 * the method returns {@code null}. If the tree produces a constant expression with value {@code null}, the method returns
 * {@link #NULL} as a placeholder.
 * 

* In general the evaluator may return boxed values of constants, {@link #NULL} to indicate the expression is a null constant, * or a {@link #NOT_NULL} to indicate the expression is some non-null value, but not a primitive or boxed primitive value. * Note that {@link #NOT_NULL} does not undergo such sophisticated analysis as in Flow, where branches are checked. *

* If resolving of constants is enabled, the method will attempt to resolve values of referenced symbols. If * enhanced processing is enabled, analysis beyond JLS specification is allowed. The enhancements include: *

    *
  • tracking of null and definitely non-null values *
  • casts to boxing reference types (i.e. Integer, Long) *
  • casts to assignment-compatible types *
  • inspection of values of final local variables and final non-static fields with initializers *
      * Results of enhanced analysis do not interfere with strict JLS processing, although results are cached. *

      * A convenience method is provided for strict JLS processing. *

      * The evaluator tries to cache values for branching points (conditions in if, switch, cycles, assert) and for field/variable * initializers and assignments. * *

      * Future enhancements may include pluggable evaluation of well-known functions, like Math.* etc (if pluggable, then * library support could declare const patterns or plug in an auxiliary evaluator). *

      * Annotations like @NotNull could be exploited to produce known-null/not null values * * @author lahvac */ public class ArithmeticUtilities { /** * Evaluates constant expression at tree path 'tp'. Uses strict JLS rules, and optionally resolves constants. * * @param info context * @param tp path for the expression to evaluate * @param resolveCompileTimeConstants if true, referenced symbols are resolved according to JLS compile-time constant definition * @return constant value, or {@code null} if the tree does not produce a compile-time constant expression. */ public static Number compute(CompilationInfo info, TreePath tp, boolean resolveCompileTimeConstants) { Object o = compute(info, tp, resolveCompileTimeConstants, false); return o instanceof Number ? (Number)o : null; } /** * Placeholder value for 'null' constant value. New string is constructed so it is not interned and * could be compared using ==. "null" value is chosen so that NULL.toString() provides null for String * concatenation operation. */ // something other than String, so instanceof- checks in clients won't detect the result as String const static final Object NULL = new Object() { @Override public String toString() { return "null"; } }; /** * Identifies some value, which is not null. */ public static final Object NOT_NULL = new Object(); private static final Object UNKNOWN = new Object(); public static boolean isRealValue(Object o) { return !(o == null || o == NULL || o == NOT_NULL); } public static boolean isNull(Object o) { return o == NULL; } public static boolean isNeverNull(Object o) { return o == NOT_NULL; } /** * Evaluates the constant expression encoded by tree at 'tp' path. See {@link ArithmeticUtilities} for more information. * Always check the result value for instanceof before using the value. Without the check, only != null test * is valid and has meaning "the expression is known to be constant". * * @param info context * @param tp tree path for the expression * @param resolveCompileTimeConstants if false, symbol resolution is enabled * @param enhanced if true, improved analysis is enabled. If false, strict JLS rules apply. * @return */ public static Object compute(CompilationInfo info, TreePath tp, boolean resolveCompileTimeConstants, boolean enhanced) { // examine parent, if the expression is in some condition/cycle, it might be already evaluated boolean save = false; ElementValue v = null; Map cache = null; if (tp.getParentPath() != null) { Tree parentL = tp.getParentPath().getLeaf(); switch (parentL.getKind()) { case IF: case DO_WHILE_LOOP: case CONDITIONAL_EXPRESSION: case FOR_LOOP: case ASSIGNMENT: case VARIABLE: save = true; break; case ASSERT: save = ((AssertTree)parentL).getCondition() == tp.getLeaf(); break; } if (save) { cache = VisitorImpl.getValueCache(info); v = cache.get(tp.getLeaf()); if (v != null) { if (enhanced && v.constant != null) { return v.constant == UNKNOWN ? null : v.constant; } else if (!enhanced && v.jlsConstant != null) { return v.jlsConstant == UNKNOWN ? null : v.jlsConstant; } } } } Object o; try { o = new VisitorImpl(info, resolveCompileTimeConstants, enhanced).scan(tp, null); } catch (ArithmeticException | IndexOutOfBoundsException | IllegalArgumentException ex) { o = null; } if (save) { if (v == null) { v = new ElementValue(); cache.put(tp.getLeaf(), v); } if (enhanced) { v.constant = o == null ? UNKNOWN : o; } else { v.jlsConstant = o == null ? UNKNOWN : o; } } return o; } private static final class VisitorImpl extends ErrorAwareTreePathScanner { // PENDING: instanceof String could be handled private static final Set ACCEPTED_KINDS = EnumSet.of( MULTIPLY, DIVIDE, REMAINDER, PLUS, MINUS, LEFT_SHIFT, RIGHT_SHIFT, UNSIGNED_RIGHT_SHIFT, AND, XOR, OR, UNARY_MINUS, UNARY_PLUS, PARENTHESIZED, IDENTIFIER, MEMBER_SELECT, INT_LITERAL, LONG_LITERAL, FLOAT_LITERAL, DOUBLE_LITERAL, CHAR_LITERAL, BOOLEAN_LITERAL, NULL_LITERAL, STRING_LITERAL, // boolean ops CONDITIONAL_AND, CONDITIONAL_OR, CONDITIONAL_EXPRESSION, // relational ops EQUAL_TO, NOT_EQUAL_TO, GREATER_THAN, GREATER_THAN_EQUAL, LESS_THAN, LESS_THAN_EQUAL, TYPE_CAST, NEW_CLASS, NEW_ARRAY ); private static final EnumSet PRIMITIVE_KINDS = EnumSet.of( TypeKind.BOOLEAN, TypeKind.CHAR, TypeKind.BYTE, TypeKind.SHORT, TypeKind.INT, TypeKind.LONG, TypeKind.DOUBLE, TypeKind.FLOAT ); private final CompilationInfo info; private final boolean resolveCompileTimeConstants; private final boolean enhanceProcessing; public VisitorImpl(CompilationInfo info, boolean resolveCompileTimeConstants, boolean enhanceProcessing) { this.info = info; this.resolveCompileTimeConstants = resolveCompileTimeConstants; this.enhanceProcessing = enhanceProcessing; } @Override public Object scan(TreePath tree, Void p) { if (!ACCEPTED_KINDS.contains(tree.getLeaf().getKind())) return null; return super.scan(tree, p); } @Override public Object scan(Tree tree, Void p) { if (tree == null) return null; if (!ACCEPTED_KINDS.contains(tree.getKind())) return null; return super.scan(tree, p); } private Object resolveTypeCast(TypeCastTree node, TypeMirror target, Object result, Void p) { if (target.getKind() != TypeKind.DECLARED) { return null; } DeclaredType dt = (DeclaredType)target; TypeElement e = (TypeElement)dt.asElement(); if (enhanceProcessing) { // accept null constant typecasted to anything as null if (result == NULL) { return NULL; } else if (result == NOT_NULL) { // some unspecified reference type... return result; } } String qn = e.getQualifiedName().toString(); // type casts to String are permitted by JLS 15.28 if ("java.lang.String".equals(qn)) { // NOI18N return result instanceof String ? result : null; } else if (!enhanceProcessing) { // other typecasts are not lised in JLS 15.28 return null; } TypeMirror castee = info.getTrees().getTypeMirror(new TreePath(getCurrentPath(), node.getExpression())); if (!Utilities.isValidType(castee)) { return null; } if (info.getTypes().isAssignable(castee, target)) { return result; } // a constant of primitive type may be casted / wrapped to the wrapper type switch (qn) { case "java.lang.Boolean": // NOI18N if (result instanceof Boolean) return result; break; case "java.lang.Byte": // NOI18N // the casted expression may be typed as Byte or byte; if (result instanceof Number && castee != null && castee.getKind() == TypeKind.BYTE) return ((Number)result).byteValue(); break; case "java.lang.Character": // NOI18N if (result instanceof Number && castee != null && castee.getKind() == TypeKind.CHAR) return Character.valueOf((char)((Number)result).intValue()); break; case "java.lang.Double": // NOI18N if (result instanceof Number && castee != null && castee.getKind() == TypeKind.DOUBLE) return ((Number)result).doubleValue(); break; case "java.lang.Float": // NOI18N if (result instanceof Number && castee != null && castee.getKind() == TypeKind.FLOAT) return ((Number)result).floatValue(); break; case "java.lang.Integer": // NOI18N if (result instanceof Number && castee != null && castee.getKind() == TypeKind.INT) return ((Number)result).intValue(); break; case "java.lang.Long": // NOI18N if (result instanceof Number && castee != null && castee.getKind() == TypeKind.LONG) return ((Number)result).longValue(); break; case "java.lang.Short": // NOI18N if (result instanceof Number && castee != null && castee.getKind() == TypeKind.SHORT) return ((Number)result).shortValue(); break; } return null; } @Override public Object visitTypeCast(TypeCastTree node, Void p) { Object op = scan(node.getExpression(), p); if (op == null) { return null; } Object result = null; TypeMirror tm = info.getTrees().getTypeMirror(new TreePath(getCurrentPath(), node.getType())); if (!Utilities.isValidType(tm)) { return null; } // casting to some non-char primitive type, perform unary numeric promotion JLS 5.6.1 if (tm.getKind() != TypeKind.CHAR && op instanceof Character) { op = Integer.valueOf(((Character)op).charValue()); } // accept unboxing conversion, primitive conversion switch (tm.getKind()) { case BOOLEAN: if (op instanceof Boolean) result = op; break; case BYTE: if (op instanceof Number) result = ((Number)op).byteValue(); break; case CHAR: if (op instanceof Character) result = op; if (op instanceof Number) result = Character.valueOf((char)((Number)op).intValue()); break; case DOUBLE: if (op instanceof Number) result = ((Number)op).doubleValue(); break; case FLOAT: if (op instanceof Number) result = ((Number)op).floatValue(); break; case INT: if (op instanceof Number) result = ((Number)op).intValue(); break; case LONG: if (op instanceof Number) result = ((Number)op).longValue(); break; case SHORT: if (op instanceof Number) result = ((Number)op).shortValue(); break; default: return resolveTypeCast(node, tm, op, p); } return result; } /** * If enhanced processing is on, report a NOT_NULL, since the result * of new arr expression may not be null. */ @Override public Object visitNewArray(NewArrayTree node, Void p) { return enhanceProcessing ? NOT_NULL : null; } @Override public Object visitNewClass(NewClassTree node, Void p) { return enhanceProcessing ? NOT_NULL : null; } @Override public Object visitLiteral(LiteralTree node, Void p) { if (node.getKind() == NULL_LITERAL) { return enhanceProcessing ? NULL : null; } return node.getValue(); } @Override public Object visitIdentifier(IdentifierTree node, Void p) { return resolve(); } @Override public Object visitMemberSelect(MemberSelectTree node, Void p) { return resolve(); } private static Map getValueCache(CompilationInfo info) { Map cache = (Map)info.getCachedValue(CONST_EVAL_KEY); if (cache == null) { cache = new HashMap(7); info.putCachedValue(CONST_EVAL_KEY, cache, CompilationInfo.CacheClearPolicy.ON_TASK_END); } return cache; } /** * Attempts to resolve Elements value. For JLS-strict mode, values are only resolved for fields. Null value * or known-not-null values are ignored. For enhanced mode, null & non-null may be returned for further processing * * @param info * @param path * @param enhanceProcessing * @return */ private static Object resolveElementValue(CompilationInfo info, TreePath path, boolean enhanceProcessing) { Element el = info.getTrees().getElement(path); if (el == null) { return null; } Map cache = getValueCache(info); ElementValue entry = cache.get(el); if (entry != null) { Object v = enhanceProcessing ? entry.constant : entry.jlsConstant; if (v != null) { return v != UNKNOWN ? v : null; } } if (el == null) { return null; } Object obj = null; if (el.getKind() == ElementKind.FIELD) { obj = ((VariableElement) el).getConstantValue(); // if enhanced processing is enabled, give other chance to fields that are static final if (obj == null && (!el.getModifiers().contains(Modifier.FINAL) || !enhanceProcessing)) { obj = UNKNOWN; } } else if (el.getKind() == ElementKind.LOCAL_VARIABLE) { obj = ((VariableElement) el).getConstantValue(); if (obj == null && (!el.getModifiers().contains(Modifier.FINAL) || !enhanceProcessing)) { obj = UNKNOWN; } } EVAL: if (obj == null) { TreePath varPath = info.getTrees().getPath(el); if (varPath != null && varPath.getLeaf().getKind() == Tree.Kind.VARIABLE) { // #262309: if variable is errnoeously referenced from its own initializer, we must not recurse. for (Tree t : path) { if (t == varPath.getLeaf()) { break EVAL; } if (StatementTree.class.isAssignableFrom(t.getKind().asInterface())) { break; } } VariableTree vt = (VariableTree)varPath.getLeaf(); if (vt.getInitializer() != null) { VisitorImpl recurse = new VisitorImpl(info, true, enhanceProcessing); try { obj = recurse.scan(new TreePath(varPath, vt.getInitializer()), null); } catch (ArithmeticException | IndexOutOfBoundsException | IllegalArgumentException ex) { // no op, obj is already null. } } } } else if (entry == null) { // obj was resolved as a constant value and entry still does not exist for it -> save memory return obj != UNKNOWN ? obj : null; } if (entry == null) { entry = new ElementValue(); cache.put(el, entry); } if (obj == null) { obj = UNKNOWN; } if (enhanceProcessing) { entry.constant = obj; } else { entry.jlsConstant = obj; if (obj == NULL || obj == NOT_NULL) { return null; } } return obj != UNKNOWN ? obj : null; } private Object resolve() { if (!resolveCompileTimeConstants) { return null; } return resolveElementValue(info, getCurrentPath(), enhanceProcessing); } private interface BinaryOp { public Object eval(Object left, Object right); } private static final BinaryOp OP_EQUAL = new BinaryOp() { public Object eval(Object left, Object right) { return left.equals(right); }}; private Object numericBinaryOp(BinaryOp op, Object left, Object right) { return null; } @Override public Object visitConditionalExpression(ConditionalExpressionTree node, Void p) { Object condition = scan(node.getCondition(), p); if (condition == Boolean.TRUE) { return scan(node.getTrueExpression(), p); } else if (condition == Boolean.FALSE) { return scan(node.getFalseExpression(), p); } if (enhanceProcessing) { Object first = scan(node.getTrueExpression(), p); Object second = scan(node.getFalseExpression(), p); if (first == NULL && second == NULL) { return NULL; } else if (first != null && second != null) { return NOT_NULL; } } // indeterminate return null; } @Override public Object visitBinary(BinaryTree node, Void p) { Object left = scan(node.getLeftOperand(), p); Object right = scan(node.getRightOperand(), p); // JSL 5.6.2, binary numeric promotion + JLS 5.1.2, widening primitive conversion for char values. // other value types are handled by the Number class in JDK. Chars may be promoted further to float / double. if (left instanceof Character && !(right instanceof String)) { left = Integer.valueOf(((Character)left).charValue()); } if (right instanceof Character && !(left instanceof String)) { right = Integer.valueOf(((Character)right).charValue()); } if (left != null && right != null) { Object result = null; switch (node.getKind()) { case EQUAL_TO: if (left instanceof Number && right instanceof Number) { result = numericBinaryOp(OP_EQUAL, left, right); Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() == rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() == rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() == rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() == rn.intValue(); } else { return null; } } else if (left instanceof Boolean && right instanceof Boolean) { return left.equals(right); } else if (left == NULL || right == NULL) { // cannot accept primitives, boxing conversion does not apply TypeMirror m = info.getTrees().getTypeMirror( new TreePath(getCurrentPath(), left == NULL ? node.getRightOperand() : node.getLeftOperand() )); if (Utilities.isValidType(m) && !PRIMITIVE_KINDS.contains(m.getKind())) { result = left == right; } } break; case NOT_EQUAL_TO: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() != rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() != rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() != rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() != rn.intValue(); } else { return null; } } else if (left instanceof Boolean && right instanceof Boolean) { return left.equals(right); } else if (enhanceProcessing && (left == NULL || right == NULL)) { // cannot accept primitives, boxing conversion does not apply TypeMirror m = info.getTrees().getTypeMirror(new TreePath(getCurrentPath(), left == NULL ? node.getRightOperand() : node.getLeftOperand())); if (Utilities.isValidType(m) && !PRIMITIVE_KINDS.contains(m.getKind())) { result = left != right; } } break; case LESS_THAN: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() < rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() < rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() < rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() < rn.intValue(); } else { return null; } } break; case LESS_THAN_EQUAL: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() <= rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() <= rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() <= rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() <= rn.intValue(); } else { return null; } } break; case GREATER_THAN: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() > rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() > rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() > rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() > rn.intValue(); } else { return null; } } break; case GREATER_THAN_EQUAL: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() >= rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() >= rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() >= rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() >= rn.intValue(); } else { return null; } } break; case MULTIPLY: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() * rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() * rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() * rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() * rn.intValue(); } else { return null; } } break; case DIVIDE: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() / rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() / rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() / rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() / rn.intValue(); } else { return null; } } boolean a = true; boolean b = false; boolean c = a & b; break; case REMAINDER: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() % rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() % rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() % rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() % rn.intValue(); } else { return null; } } break; case MINUS: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() - rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() - rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() - rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() - rn.intValue(); } else { return null; } } break; case LEFT_SHIFT: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Long || right instanceof Long) { result = ln.longValue() << rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() << rn.intValue(); } else { return null; } } break; case RIGHT_SHIFT: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Long || right instanceof Long) { result = ln.longValue() >> rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() >> rn.intValue(); } else { return null; } } break; case UNSIGNED_RIGHT_SHIFT: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Long || right instanceof Long) { result = ln.longValue() >>> rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() >>> rn.intValue(); } else { return null; } } break; // PLUS is also supported for String operands. `null' value is represented by String containing "null", // so it will produce the correct concatenation result. case PLUS: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Double || right instanceof Double) { result = ln.doubleValue() + rn.doubleValue(); } else if (left instanceof Float || right instanceof Float) { result = ln.floatValue() + rn.floatValue(); } else if (left instanceof Long || right instanceof Long) { result = ln.longValue() + rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() + rn.intValue(); } else { return null; } } else if (left instanceof String) { if (right != NOT_NULL) { result = (String)left + right; } } else if (right instanceof String) { if (left != NOT_NULL) { result = left + (String)right; } } break; // AND, OR apply as well to booleans case CONDITIONAL_AND: if (left instanceof Boolean && right instanceof Boolean) { result = ((Boolean)left) && ((Boolean)right); } break; case CONDITIONAL_OR: if (left instanceof Boolean && right instanceof Boolean) { result = ((Boolean)left) || ((Boolean)right); } break; case XOR: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Long || right instanceof Long) { result = ln.longValue() ^ rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() ^ rn.intValue(); } else { return null; } } else if (left instanceof Boolean && right instanceof Boolean) { result = ((Boolean)left) ^ ((Boolean)right); } break; case AND: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Long || right instanceof Long) { result = ln.longValue() & rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() & rn.intValue(); } else { return null; } } else if (left instanceof Boolean && right instanceof Boolean) { result = ((Boolean)left) & ((Boolean)right); } break; case OR: if (left instanceof Number && right instanceof Number) { Number ln = (Number)left; Number rn = (Number)right; if (left instanceof Long || right instanceof Long) { result = ln.longValue() | rn.longValue(); } else if (integerLike(ln) || integerLike(rn)) { result = ln.intValue() | rn.intValue(); } else { return null; } } else if (left instanceof Boolean && right instanceof Boolean) { result = ((Boolean)left) | ((Boolean)right); } break; } return result; } return null; } @Override public Object visitUnary(UnaryTree node, Void p) { Object op = scan(node.getExpression(), p); if (op != null) { Object result = null; if (op instanceof Character) { op = Integer.valueOf(((Character)op).charValue()); } switch (node.getKind()) { case BITWISE_COMPLEMENT: if (op instanceof Long) { result = ~((Long)op).longValue(); } else if (op instanceof Number && integerLike((Number)op)) { result = ~(((Number)op).intValue()); } break; case LOGICAL_COMPLEMENT: if (op instanceof Boolean) { result = !((Boolean)op).booleanValue(); } break; case UNARY_MINUS: if (op instanceof Number) { Number nop = (Number)op; if (op instanceof Double) { result = -nop.doubleValue(); } else if (op instanceof Float) { result = -nop.floatValue(); } else if (op instanceof Long) { result = -nop.longValue(); } else if (integerLike(nop)) { result = -nop.intValue(); } else { return null; } } break; case UNARY_PLUS: if (op instanceof Number) { result = op; } break; } return result; } return super.visitUnary(node, p); } private static boolean integerLike(Number n) { return n instanceof Integer || n instanceof Short || n instanceof Byte; } } /** * Performs implicit conversion of a literal value to the target type. * Returns {@code null} if the conversion is not possible (incovertible, lossy * conversion etc). * * @param val the value * @param convertTo the target type * @return new literal value */ public static Object implicitConversion(CompilationInfo info, Object val, TypeMirror convertTo) { if (!convertTo.getKind().isPrimitive()) { if (Utilities.isPrimitiveWrapperType(convertTo)) { convertTo = info.getTypes().unboxedType(convertTo); } else if (isNull(val)) { return val; } else { // possibly a String ? if (val instanceof String && convertTo.getKind() == TypeKind.DECLARED) { Element el = info.getTypes().asElement(convertTo); if (el.getKind() == ElementKind.CLASS && ((TypeElement)el).getQualifiedName().contentEquals("java.lang.String")) { return val; } } return null; } } switch (convertTo.getKind()) { case BOOLEAN: if (val instanceof Boolean) { return val; } else { return null; } case BYTE: { if (val instanceof Short || val instanceof Long || val instanceof Integer) { long l = ((Number)val).longValue(); if (l >= 0 && l <= 0xff) { return (byte)l; } } else if (val instanceof Character) { return (byte)((Character)val).charValue(); } return null; } case CHAR: if (val instanceof Character) { return val; } else if (val instanceof Short) { short n = (Short)val; if (n < (1 << 15)) { return Character.valueOf((char)n); } } else if (val instanceof Integer) { int n = (Integer)val; if ( n < (1 << 16)) { return Character.valueOf((char)n); } } return null; case DOUBLE: if (val instanceof Number) { return ((Number)val).doubleValue(); } else if (val instanceof Character) { return Double.valueOf((Character)val); } return null; case FLOAT: if (val instanceof Double) { return null; } if (val instanceof Number) { return ((Number)val).floatValue(); } else if (val instanceof Character) { return Double.valueOf((Character)val); } return null; case INT: if (val instanceof Short || val instanceof Long || val instanceof Integer) { long l = ((Number)val).longValue(); if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) { return (int)l; } } else if (val instanceof Character) { return (int)((Character)val); } return null; case LONG: if (val instanceof Character) { return (long)((Character)val).charValue(); } else if (val instanceof Double || val instanceof Float) { return null; } else if (val instanceof Number) { return ((Number)val).longValue(); } return null; case SHORT: if (val instanceof Short || val instanceof Long || val instanceof Integer) { long l = ((Number)val).longValue(); if (l >= Short.MIN_VALUE && l <= Short.MAX_VALUE) { return (short)l; } } else if (val instanceof Character) { return (short)((Character)val).charValue(); } return null; default: return null; } } private static final String CONST_EVAL_KEY = ArithmeticUtilities.class.getName(); /** * Cached information about an element once seen; contains the value, */ private static class ElementValue { private Object jlsConstant; private Object constant; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy