Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.netbeans.modules.java.hints.ArithmeticUtilities Maven / Gradle / Ivy
/*
* 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;
}
}