blob: ce38729027d9272c7c2f0672892edae47a2319ba [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.psi.impl.source.tree.java;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.ResolveScopeManager;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.LeafElement;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.impl.source.tree.injected.StringLiteralEscaper;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.text.LiteralFormatUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
public class PsiLiteralExpressionImpl
extends ExpressionPsiElement
implements PsiLiteralExpression, PsiLanguageInjectionHost, ContributedReferenceHost {
@NonNls private static final String QUOT = """;
@NonNls public static final String HEX_PREFIX = "0x";
@NonNls public static final String BIN_PREFIX = "0b";
@NonNls public static final String _2_IN_31 = Long.toString(-1L << 31).substring(1);
@NonNls public static final String _2_IN_63 = Long.toString(-1L << 63).substring(1);
public static final TokenSet INTEGER_LITERALS = TokenSet.create(JavaTokenType.INTEGER_LITERAL, JavaTokenType.LONG_LITERAL);
public static final TokenSet REAL_LITERALS = TokenSet.create(JavaTokenType.FLOAT_LITERAL, JavaTokenType.DOUBLE_LITERAL);
public static final TokenSet NUMERIC_LITERALS = TokenSet.orSet(INTEGER_LITERALS, REAL_LITERALS);
public PsiLiteralExpressionImpl() {
super(JavaElementType.LITERAL_EXPRESSION);
}
@Override
public PsiType getType() {
final IElementType type = getLiteralElementType();
if (type == JavaTokenType.INTEGER_LITERAL) {
return PsiType.INT;
}
if (type == JavaTokenType.LONG_LITERAL) {
return PsiType.LONG;
}
if (type == JavaTokenType.FLOAT_LITERAL) {
return PsiType.FLOAT;
}
if (type == JavaTokenType.DOUBLE_LITERAL) {
return PsiType.DOUBLE;
}
if (type == JavaTokenType.CHARACTER_LITERAL) {
return PsiType.CHAR;
}
if (type == JavaTokenType.STRING_LITERAL) {
PsiManagerEx manager = getManager();
GlobalSearchScope resolveScope = ResolveScopeManager.getElementResolveScope(this);
return PsiType.getJavaLangString(manager, resolveScope);
}
if (type == JavaTokenType.TRUE_KEYWORD || type == JavaTokenType.FALSE_KEYWORD) {
return PsiType.BOOLEAN;
}
if (type == JavaTokenType.NULL_KEYWORD) {
return PsiType.NULL;
}
return null;
}
public IElementType getLiteralElementType() {
return getFirstChildNode().getElementType();
}
public String getCanonicalText() {
final TreeElement literal = getFirstChildNode();
final IElementType type = literal.getElementType();
return NUMERIC_LITERALS.contains(type) ? LiteralFormatUtil.removeUnderscores(literal.getText()) : literal.getText();
}
@Override
public Object getValue() {
final IElementType type = getLiteralElementType();
String text = NUMERIC_LITERALS.contains(type) ? getCanonicalText().toLowerCase(Locale.ENGLISH) : getCanonicalText();
final int textLength = text.length();
if (type == JavaTokenType.INTEGER_LITERAL) {
try {
if (text.startsWith(HEX_PREFIX)) {
// should fit in 32 bits
final long value = parseDigits(text.substring(2), 4, 32);
return Integer.valueOf((int)value);
}
if (text.startsWith(BIN_PREFIX)) {
// should fit in 32 bits
final long value = parseDigits(text.substring(2), 1, 32);
return Integer.valueOf((int)value);
}
if (StringUtil.startsWithChar(text, '0')) {
// should fit in 32 bits
final long value = parseDigits(text, 3, 32);
return Integer.valueOf((int)value);
}
final long l = Long.parseLong(text, 10);
if (text.equals(_2_IN_31)) return Integer.valueOf((int)l);
long converted = (int)l;
return l == converted ? Integer.valueOf((int)l) : null;
}
catch (NumberFormatException e) {
return null;
}
}
if (type == JavaTokenType.LONG_LITERAL) {
if (StringUtil.endsWithChar(text, 'L') || StringUtil.endsWithChar(text, 'l')) {
text = text.substring(0, textLength - 1);
}
try {
if (text.startsWith(HEX_PREFIX)) {
return parseDigits(text.substring(2), 4, 64);
}
if (text.startsWith(BIN_PREFIX)) {
return parseDigits(text.substring(2), 1, 64);
}
if (StringUtil.startsWithChar(text, '0')) {
// should fit in 64 bits
return parseDigits(text, 3, 64);
}
if (_2_IN_63.equals(text)) return Long.valueOf(-1L << 63);
return Long.valueOf(text, 10);
}
catch (NumberFormatException e) {
return null;
}
}
if (type == JavaTokenType.FLOAT_LITERAL) {
try {
return Float.valueOf(text);
}
catch (NumberFormatException e) {
return null;
}
}
if (type == JavaTokenType.DOUBLE_LITERAL) {
try {
return Double.valueOf(text);
}
catch (NumberFormatException e) {
return null;
}
}
if (type == JavaTokenType.CHARACTER_LITERAL) {
if (StringUtil.endsWithChar(text, '\'')) {
if (textLength == 1) return null;
text = text.substring(1, textLength - 1);
}
else {
text = text.substring(1, textLength);
}
StringBuilder chars = new StringBuilder();
boolean success = parseStringCharacters(text, chars, null);
if (!success) return null;
if (chars.length() != 1) return null;
return Character.valueOf(chars.charAt(0));
}
if (type == JavaTokenType.STRING_LITERAL) {
String innerText = getInnerText();
return innerText == null ? null : internedParseStringCharacters(innerText);
}
if (type == JavaTokenType.TRUE_KEYWORD) {
return Boolean.TRUE;
}
if (type == JavaTokenType.FALSE_KEYWORD) {
return Boolean.FALSE;
}
return null;
}
@Nullable
public String getInnerText() {
String text = getCanonicalText();
int textLength = text.length();
if (StringUtil.endsWithChar(text, '\"')) {
if (textLength == 1) return null;
text = text.substring(1, textLength - 1);
}
else {
if (text.startsWith(QUOT) && text.endsWith(QUOT) && textLength > QUOT.length()) {
text = text.substring(QUOT.length(), textLength - QUOT.length());
}
else {
return null;
}
}
return text;
}
// convert text to number according to radix specified
// if number is more than maxBits bits long, throws NumberFormatException
private static long parseDigits(final String text, final int bitsInRadix, final int maxBits) throws NumberFormatException {
final int radix = 1 << bitsInRadix;
final int textLength = text.length();
if (textLength == 0) {
throw new NumberFormatException(text);
}
long integer = textLength == 1 ? 0 : Long.parseLong(text.substring(0, textLength - 1), radix);
if ((integer & (-1L << (maxBits - bitsInRadix))) != 0) {
throw new NumberFormatException(text);
}
final int lastDigit = Character.digit(text.charAt(textLength - 1), radix);
if (lastDigit == -1) {
throw new NumberFormatException(text);
}
integer <<= bitsInRadix;
integer |= lastDigit;
return integer;
}
@Nullable
private static String internedParseStringCharacters(final String chars) {
final StringBuilder outChars = new StringBuilder(chars.length());
final boolean success = parseStringCharacters(chars, outChars, null);
return success ? outChars.toString() : null;
}
public static boolean parseStringCharacters(@NotNull String chars, @NotNull StringBuilder outChars, @Nullable int[] sourceOffsets) {
assert sourceOffsets == null || sourceOffsets.length == chars.length()+1;
if (chars.indexOf('\\') < 0) {
outChars.append(chars);
if (sourceOffsets != null) {
for (int i = 0; i < sourceOffsets.length; i++) {
sourceOffsets[i] = i;
}
}
return true;
}
int index = 0;
final int outOffset = outChars.length();
while (index < chars.length()) {
char c = chars.charAt(index++);
if (sourceOffsets != null) {
sourceOffsets[outChars.length()-outOffset] = index - 1;
sourceOffsets[outChars.length() + 1 -outOffset] = index;
}
if (c != '\\') {
outChars.append(c);
continue;
}
if (index == chars.length()) return false;
c = chars.charAt(index++);
switch (c) {
case'b':
outChars.append('\b');
break;
case't':
outChars.append('\t');
break;
case'n':
outChars.append('\n');
break;
case'f':
outChars.append('\f');
break;
case'r':
outChars.append('\r');
break;
case'"':
outChars.append('"');
break;
case'\'':
outChars.append('\'');
break;
case'\\':
outChars.append('\\');
break;
case'0':
case'1':
case'2':
case'3':
case'4':
case'5':
case'6':
case'7':
char startC = c;
int v = (int)c - '0';
if (index < chars.length()) {
c = chars.charAt(index++);
if ('0' <= c && c <= '7') {
v <<= 3;
v += c - '0';
if (startC <= '3' && index < chars.length()) {
c = chars.charAt(index++);
if ('0' <= c && c <= '7') {
v <<= 3;
v += c - '0';
}
else {
index--;
}
}
}
else {
index--;
}
}
outChars.append((char)v);
break;
case'u':
// uuuuu1234 is valid too
while (index != chars.length() && chars.charAt(index) == 'u') {
index++;
}
if (index + 4 <= chars.length()) {
try {
int code = Integer.parseInt(chars.substring(index, index + 4), 16);
//line separators are invalid here
if (code == 0x000a || code == 0x000d) return false;
c = chars.charAt(index);
if (c == '+' || c == '-') return false;
outChars.append((char)code);
index += 4;
}
catch (Exception e) {
return false;
}
}
else {
return false;
}
break;
default:
return false;
}
if (sourceOffsets != null) {
sourceOffsets[outChars.length()-outOffset] = index;
}
}
return true;
}
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof JavaElementVisitor) {
((JavaElementVisitor)visitor).visitLiteralExpression(this);
}
else {
visitor.visitElement(this);
}
}
@Override
public String toString() {
return "PsiLiteralExpression:" + getText();
}
@Override
public boolean isValidHost() {
return getValue() instanceof String;
}
@Override
@NotNull
public PsiReference[] getReferences() {
IElementType type = getLiteralElementType();
if (type != JavaTokenType.STRING_LITERAL && type != JavaTokenType.INTEGER_LITERAL) {
return PsiReference.EMPTY_ARRAY; // there are references in int literals in SQL API parameters
}
return PsiReferenceService.getService().getContributedReferences(this);
}
@Override
public PsiLanguageInjectionHost updateText(@NotNull final String text) {
TreeElement valueNode = getFirstChildNode();
assert valueNode instanceof LeafElement;
((LeafElement)valueNode).replaceWithText(text);
return this;
}
@Override
@NotNull
public LiteralTextEscaper<PsiLiteralExpressionImpl> createLiteralTextEscaper() {
return new StringLiteralEscaper<PsiLiteralExpressionImpl>(this);
}
}