blob: b356698428d87e4374bb1da88c388bc0c0c6dc8d [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.compiled;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiNameHelper;
import com.intellij.psi.PsiReferenceList;
import com.intellij.psi.impl.cache.ModifierFlags;
import com.intellij.psi.impl.cache.TypeInfo;
import com.intellij.psi.impl.java.stubs.*;
import com.intellij.psi.impl.java.stubs.impl.*;
import com.intellij.psi.stubs.PsiFileStub;
import com.intellij.psi.stubs.StubElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.cls.ClsFormatException;
import com.intellij.util.io.StringRef;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.*;
import java.lang.reflect.Array;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import static com.intellij.psi.CommonClassNames.*;
/**
* @author max
*/
public class StubBuildingVisitor<T> extends ClassVisitor {
private static final Pattern REGEX_PATTERN = Pattern.compile("(?<=[^\\$\\.])\\${1}(?=[^\\$])"); // disallow .$ or $$
public static final String DOUBLE_POSITIVE_INF = "1.0 / 0.0";
public static final String DOUBLE_NEGATIVE_INF = "-1.0 / 0.0";
public static final String DOUBLE_NAN = "0.0d / 0.0";
public static final String FLOAT_POSITIVE_INF = "1.0f / 0.0";
public static final String FLOAT_NEGATIVE_INF = "-1.0f / 0.0";
public static final String FLOAT_NAN = "0.0f / 0.0";
private static final int ASM_API = Opcodes.ASM5;
@NonNls private static final String SYNTHETIC_CLASS_INIT_METHOD = "<clinit>";
@NonNls private static final String SYNTHETIC_INIT_METHOD = "<init>";
private final T mySource;
private final InnerClassSourceStrategy<T> myInnersStrategy;
private final StubElement myParent;
private final int myAccess;
private final String myShortName;
private PsiClassStub myResult;
private PsiModifierListStub myModList;
public StubBuildingVisitor(T classSource, InnerClassSourceStrategy<T> innersStrategy, StubElement parent, int access, String shortName) {
super(ASM_API);
mySource = classSource;
myInnersStrategy = innersStrategy;
myParent = parent;
myAccess = access;
myShortName = shortName;
}
public PsiClassStub<?> getResult() {
return myResult;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
String fqn;
String shortName;
if (myShortName != null && name.endsWith(myShortName)) {
shortName = myShortName;
fqn = name.length() == shortName.length()
? shortName : getClassName(name.substring(0, name.length() - shortName.length() - 1)) + "." + shortName;
}
else {
fqn = getClassName(name);
shortName = PsiNameHelper.getShortClassName(fqn);
}
int flags = myAccess | access;
boolean isDeprecated = (flags & Opcodes.ACC_DEPRECATED) != 0;
boolean isInterface = (flags & Opcodes.ACC_INTERFACE) != 0;
boolean isEnum = (flags & Opcodes.ACC_ENUM) != 0;
boolean isAnnotationType = (flags & Opcodes.ACC_ANNOTATION) != 0;
byte stubFlags = PsiClassStubImpl.packFlags(isDeprecated, isInterface, isEnum, false, false, isAnnotationType, false, false);
myResult = new PsiClassStubImpl(JavaStubElementTypes.CLASS, myParent, fqn, shortName, null, stubFlags);
LanguageLevel languageLevel = convertFromVersion(version);
((PsiClassStubImpl)myResult).setLanguageLevel(languageLevel);
myModList = new PsiModifierListStubImpl(myResult, packClassFlags(flags));
CharacterIterator signatureIterator = signature != null ? new StringCharacterIterator(signature) : null;
if (signatureIterator != null) {
try {
SignatureParsing.parseTypeParametersDeclaration(signatureIterator, myResult);
}
catch (ClsFormatException e) {
signatureIterator = null;
}
} else {
new PsiTypeParameterListStubImpl(myResult);
}
String convertedSuper;
List<String> convertedInterfaces = new ArrayList<String>();
if (signatureIterator == null) {
convertedSuper = parseClassDescription(superName, interfaces, convertedInterfaces);
} else {
try {
convertedSuper = parseClassSignature(signatureIterator, convertedInterfaces);
}
catch (ClsFormatException e) {
new PsiTypeParameterListStubImpl(myResult);
convertedSuper = parseClassDescription(superName, interfaces, convertedInterfaces);
}
}
if (isInterface) {
if (isAnnotationType) {
convertedInterfaces.remove(JAVA_LANG_ANNOTATION_ANNOTATION);
}
newReferenceList(JavaStubElementTypes.EXTENDS_LIST, myResult, ArrayUtil.toStringArray(convertedInterfaces));
newReferenceList(JavaStubElementTypes.IMPLEMENTS_LIST, myResult);
}
else {
if (convertedSuper == null ||
JAVA_LANG_OBJECT.equals(convertedSuper) ||
isEnum && (JAVA_LANG_ENUM.equals(convertedSuper) || (JAVA_LANG_ENUM + "<" + fqn + ">").equals(convertedSuper))) {
newReferenceList(JavaStubElementTypes.EXTENDS_LIST, myResult);
}
else {
newReferenceList(JavaStubElementTypes.EXTENDS_LIST, myResult, convertedSuper);
}
newReferenceList(JavaStubElementTypes.IMPLEMENTS_LIST, myResult, ArrayUtil.toStringArray(convertedInterfaces));
}
}
public static void newReferenceList(JavaClassReferenceListElementType type, StubElement parent, String... types) {
PsiReferenceList.Role role;
if (type == JavaStubElementTypes.EXTENDS_LIST) role = PsiReferenceList.Role.EXTENDS_LIST;
else if (type == JavaStubElementTypes.IMPLEMENTS_LIST) role = PsiReferenceList.Role.IMPLEMENTS_LIST;
else if (type == JavaStubElementTypes.THROWS_LIST) role = PsiReferenceList.Role.THROWS_LIST;
else if (type == JavaStubElementTypes.EXTENDS_BOUND_LIST) role = PsiReferenceList.Role.EXTENDS_BOUNDS_LIST;
else throw new IllegalArgumentException("Unknown type: " + type);
new PsiClassReferenceListStubImpl(type, parent, types, role);
}
@Nullable
private static String parseClassDescription(final String superName, final String[] interfaces, final List<String> convertedInterfaces) {
final String convertedSuper = superName != null ? getClassName(superName) : null;
for (String anInterface : interfaces) {
convertedInterfaces.add(getClassName(anInterface));
}
return convertedSuper;
}
@Nullable
private static String parseClassSignature(final CharacterIterator signatureIterator, final List<String> convertedInterfaces)
throws ClsFormatException {
final String convertedSuper = SignatureParsing.parseTopLevelClassRefSignature(signatureIterator);
while (signatureIterator.current() != CharacterIterator.DONE) {
final String ifs = SignatureParsing.parseTopLevelClassRefSignature(signatureIterator);
if (ifs == null) throw new ClsFormatException();
convertedInterfaces.add(ifs);
}
return convertedSuper;
}
private static LanguageLevel convertFromVersion(final int version) {
switch (version) {
case Opcodes.V1_1:
case Opcodes.V1_2:
case Opcodes.V1_3:
return LanguageLevel.JDK_1_3;
case Opcodes.V1_4:
return LanguageLevel.JDK_1_4;
case Opcodes.V1_5:
return LanguageLevel.JDK_1_5;
case Opcodes.V1_6:
return LanguageLevel.JDK_1_6;
case Opcodes.V1_7:
return LanguageLevel.JDK_1_7;
case Opcodes.V1_8:
return LanguageLevel.JDK_1_8;
default:
return LanguageLevel.HIGHEST;
}
}
private static int packCommonFlags(final int access) {
int flags = 0;
if ((access & Opcodes.ACC_PRIVATE) != 0) {
flags |= ModifierFlags.PRIVATE_MASK;
}
else if ((access & Opcodes.ACC_PROTECTED) != 0) {
flags |= ModifierFlags.PROTECTED_MASK;
}
else if ((access & Opcodes.ACC_PUBLIC) != 0) {
flags |= ModifierFlags.PUBLIC_MASK;
}
else {
flags |= ModifierFlags.PACKAGE_LOCAL_MASK;
}
if ((access & Opcodes.ACC_STATIC) != 0) {
flags |= ModifierFlags.STATIC_MASK;
}
if ((access & Opcodes.ACC_FINAL) != 0) {
flags |= ModifierFlags.FINAL_MASK;
}
return flags;
}
private static int packClassFlags(final int access) {
int flags = packCommonFlags(access);
if ((access & Opcodes.ACC_ABSTRACT) != 0) {
flags |= ModifierFlags.ABSTRACT_MASK;
}
return flags;
}
private static int packFieldFlags(final int access) {
int flags = packCommonFlags(access);
if ((access & Opcodes.ACC_VOLATILE) != 0) {
flags |= ModifierFlags.VOLATILE_MASK;
}
if ((access & Opcodes.ACC_TRANSIENT) != 0) {
flags |= ModifierFlags.TRANSIENT_MASK;
}
return flags;
}
private static int packMethodFlags(final int access, boolean isInterface) {
int flags = packCommonFlags(access);
if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) {
flags |= ModifierFlags.SYNCHRONIZED_MASK;
}
if ((access & Opcodes.ACC_NATIVE) != 0) {
flags |= ModifierFlags.NATIVE_MASK;
}
if ((access & Opcodes.ACC_ABSTRACT) != 0) {
flags |= ModifierFlags.ABSTRACT_MASK;
}
else if (isInterface) {
flags |= ModifierFlags.DEFENDER_MASK;
}
if ((access & Opcodes.ACC_STRICT) != 0) {
flags |= ModifierFlags.STRICTFP_MASK;
}
return flags;
}
@Override
public void visitSource(final String source, final String debug) {
((PsiClassStubImpl)myResult).setSourceFileName(source);
}
@Override
public void visitOuterClass(final String owner, final String name, final String desc) {
if (myParent instanceof PsiFileStub) {
throw new OutOfOrderInnerClassException();
}
}
@Override
public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
return new AnnotationTextCollector(desc, new AnnotationResultCallback() {
@Override
public void callback(final String text) {
new PsiAnnotationStubImpl(myModList, text);
}
});
}
@Override
public void visitInnerClass(final String name, final String outerName, final String innerName, final int access) {
if ((access & Opcodes.ACC_SYNTHETIC) != 0) return;
if (!isCorrectName(innerName) || outerName == null) return;
if ((getClassName(outerName) + "." + innerName).equals(myResult.getQualifiedName())) {
// our result is inner class
if (myParent instanceof PsiFileStub) {
throw new OutOfOrderInnerClassException();
}
}
if (!namesEqual(outerName, myResult.getQualifiedName())) {
return;
}
T innerClass = myInnersStrategy.findInnerClass(innerName, mySource);
if (innerClass != null) {
StubBuildingVisitor<T> visitor = new StubBuildingVisitor<T>(innerClass, myInnersStrategy, myResult, access, innerName);
myInnersStrategy.accept(innerClass, visitor);
}
}
private static boolean isCorrectName(String name) {
return name != null;
}
private static boolean namesEqual(String signature, String fqn) {
if (fqn == null) return true; // impossible case, just ignore
if (fqn.length() != signature.length()) return false;
int p = 0;
int dot;
while ((dot = fqn.indexOf('.', p)) >= 0) {
if (!signature.regionMatches(p, fqn, p, dot - p)) {
return false;
}
char ch = signature.charAt(dot);
if (ch != '/' && ch != '$') {
return false;
}
p = dot + 1;
}
return fqn.regionMatches(p, signature, p, fqn.length() - p);
}
@Override
@Nullable
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if ((access & Opcodes.ACC_SYNTHETIC) != 0) return null;
if (!isCorrectName(name)) return null;
byte flags = PsiFieldStubImpl.packFlags((access & Opcodes.ACC_ENUM) != 0, (access & Opcodes.ACC_DEPRECATED) != 0, false);
TypeInfo type = fieldType(desc, signature);
String initializer = constToString(value, type.text.getString(), false);
PsiFieldStub stub = new PsiFieldStubImpl(myResult, name, type, initializer, flags);
PsiModifierListStub modList = new PsiModifierListStubImpl(stub, packFieldFlags(access));
return new AnnotationCollectingVisitor(modList);
}
@NotNull
private static TypeInfo fieldType(String desc, String signature) {
if (signature != null) {
try {
return TypeInfo.fromString(SignatureParsing.parseTypeString(new StringCharacterIterator(signature, 0)));
}
catch (ClsFormatException e) {
return fieldTypeViaDescription(desc);
}
}
else {
return fieldTypeViaDescription(desc);
}
}
@NotNull
private static TypeInfo fieldTypeViaDescription(@NotNull String desc) {
Type type = Type.getType(desc);
final int dim = type.getSort() == Type.ARRAY ? type.getDimensions() : 0;
if (dim > 0) {
type = type.getElementType();
}
return new TypeInfo(getTypeText(type), (byte)dim, false, PsiAnnotationStub.EMPTY_ARRAY); //todo read annos from .class file
}
private static final String[] parameterNames = {"p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9"};
@Override
@Nullable
public MethodVisitor visitMethod(final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions) {
// JLS 13.1 says: Any constructs introduced by the compiler that do not have a corresponding construct in the source code
// must be marked as synthetic, except for default constructors and the class initialization method.
// However Scala compiler erroneously generates ACC_BRIDGE instead of ACC_SYNTHETIC flag for in-trait implementation delegation.
// See IDEA-78649
if ((access & Opcodes.ACC_SYNTHETIC) != 0) return null;
if (SYNTHETIC_CLASS_INIT_METHOD.equals(name)) return null;
// skip semi-synthetic enum methods
boolean isEnum = myResult.isEnum();
if (isEnum) {
if ("values".equals(name) && desc.startsWith("()")) return null;
if ("valueOf".equals(name) && desc.startsWith("(Ljava/lang/String;)")) return null;
}
boolean isDeprecated = (access & Opcodes.ACC_DEPRECATED) != 0;
boolean isConstructor = SYNTHETIC_INIT_METHOD.equals(name);
boolean isVarargs = (access & Opcodes.ACC_VARARGS) != 0;
boolean isAnnotationMethod = myResult.isAnnotationType();
if (!isConstructor && !isCorrectName(name)) return null;
final byte flags = PsiMethodStubImpl.packFlags(isConstructor, isAnnotationMethod, isVarargs, isDeprecated, false);
String canonicalMethodName = isConstructor ? myResult.getName() : name;
List<String> args = new ArrayList<String>();
List<String> throwables = exceptions != null ? new ArrayList<String>() : null;
int modifiersMask = packMethodFlags(access, myResult.isInterface());
final PsiMethodStubImpl stub = new PsiMethodStubImpl(myResult, StringRef.fromString(canonicalMethodName), flags, signature, args, throwables, desc, modifiersMask);
PsiModifierListStub modList = (PsiModifierListStub)stub.findChildStubByType(JavaStubElementTypes.MODIFIER_LIST);
if (isEnum && isConstructor && signature == null && args.size() >= 2 && JAVA_LANG_STRING.equals(args.get(0)) && "int".equals(args.get(1))) {
// exclude synthetic enum constructor parameters
args = args.subList(2, args.size());
}
final boolean isNonStaticInnerClassConstructor =
isConstructor && !(myParent instanceof PsiFileStub) && (myModList.getModifiersMask() & Opcodes.ACC_STATIC) == 0;
boolean parsedViaGenericSignature = stub.isParsedViaGenericSignature();
final boolean shouldSkipFirstParamForNonStaticInnerClassConstructor = !parsedViaGenericSignature && isNonStaticInnerClassConstructor;
final PsiParameterListStubImpl parameterList = new PsiParameterListStubImpl(stub);
final int paramCount = args.size();
final PsiParameterStubImpl[] paramStubs = new PsiParameterStubImpl[paramCount];
for (int i = 0; i < paramCount; i++) {
if (shouldSkipFirstParamForNonStaticInnerClassConstructor && i == 0) continue;
String arg = args.get(i);
boolean isEllipsisParam = isVarargs && i == paramCount - 1;
final TypeInfo typeInfo = TypeInfo.fromString(arg, isEllipsisParam);
String paramName = i < parameterNames.length ? parameterNames[i] : "p" + (i + 1);
PsiParameterStubImpl parameterStub = new PsiParameterStubImpl(parameterList, paramName, typeInfo, isEllipsisParam);
paramStubs [i] = parameterStub;
new PsiModifierListStubImpl(parameterStub, 0);
}
String[] thrownTypes = buildThrowsList(exceptions, throwables, parsedViaGenericSignature);
newReferenceList(JavaStubElementTypes.THROWS_LIST, stub, thrownTypes);
int localVarIgnoreCount = (access & Opcodes.ACC_STATIC) != 0 ? 0 : isConstructor && isEnum ? 3 : 1;
int paramIgnoreCount = isConstructor && isEnum ? 2 : isNonStaticInnerClassConstructor ? 1 : 0;
return new AnnotationParamCollectingVisitor(stub, modList, localVarIgnoreCount, paramIgnoreCount, paramCount, paramStubs);
}
private static String[] buildThrowsList(String[] exceptions, List<String> throwables, boolean parsedViaGenericSignature) {
if (exceptions == null) return ArrayUtil.EMPTY_STRING_ARRAY;
if (parsedViaGenericSignature && throwables != null && exceptions.length > throwables.size()) {
// There seem to be an inconsistency (or bug) in class format. For instance, java.lang.Class.forName() method has
// signature equal to "(Ljava/lang/String;)Ljava/lang/Class<*>;" (i.e. no exceptions thrown) but exceptions actually not empty,
// method throws ClassNotFoundException
parsedViaGenericSignature = false;
}
if (parsedViaGenericSignature && throwables != null) {
return ArrayUtil.toStringArray(throwables);
}
else {
String[] converted = ArrayUtil.newStringArray(exceptions.length);
for (int i = 0; i < converted.length; i++) {
converted[i] = getClassName(exceptions[i]);
}
return converted;
}
}
@NotNull
public static String parseMethodViaDescription(@NotNull String desc, @NotNull PsiMethodStubImpl stub, @NotNull List<String> args) {
final String returnType = getTypeText(Type.getReturnType(desc));
final Type[] argTypes = Type.getArgumentTypes(desc);
for (Type argType : argTypes) {
args.add(getTypeText(argType));
}
new PsiTypeParameterListStubImpl(stub);
return returnType;
}
@NotNull
public static String parseMethodViaGenericSignature(@NotNull String signature,
@NotNull PsiMethodStubImpl stub,
@NotNull List<String> args,
@Nullable List<String> throwables) throws ClsFormatException {
StringCharacterIterator iterator = new StringCharacterIterator(signature);
SignatureParsing.parseTypeParametersDeclaration(iterator, stub);
if (iterator.current() != '(') {
throw new ClsFormatException();
}
iterator.next();
while (iterator.current() != ')' && iterator.current() != CharacterIterator.DONE) {
args.add(SignatureParsing.parseTypeString(iterator));
}
if (iterator.current() != ')') {
throw new ClsFormatException();
}
iterator.next();
String returnType = SignatureParsing.parseTypeString(iterator);
while (iterator.current() == '^') {
iterator.next();
throwables.add(SignatureParsing.parseTypeString(iterator));
}
return returnType;
}
@Override
public void visitEnd() {
}
private static class AnnotationTextCollector extends AnnotationVisitor {
private final StringBuilder myBuilder = new StringBuilder();
private final AnnotationResultCallback myCallback;
private boolean hasParams = false;
private final String myDesc;
public AnnotationTextCollector(@Nullable String desc, AnnotationResultCallback callback) {
super(ASM_API);
myCallback = callback;
myDesc = desc;
if (desc != null) {
myBuilder.append('@').append(getTypeText(Type.getType(desc)));
}
}
@Override
public void visit(final String name, final Object value) {
valuePairPrefix(name);
myBuilder.append(constToString(value, null, true));
}
@Override
public void visitEnum(final String name, final String desc, final String value) {
valuePairPrefix(name);
myBuilder.append(getTypeText(Type.getType(desc))).append(".").append(value);
}
private void valuePairPrefix(final String name) {
if (!hasParams) {
hasParams = true;
if (myDesc != null) {
myBuilder.append('(');
}
} else {
myBuilder.append(',');
}
if (name != null) {
myBuilder.append(name).append('=');
}
}
@Override
public AnnotationVisitor visitAnnotation(final String name, final String desc) {
valuePairPrefix(name);
return new AnnotationTextCollector(desc, new AnnotationResultCallback() {
@Override
public void callback(final String text) {
myBuilder.append(text);
}
});
}
@Override
public AnnotationVisitor visitArray(final String name) {
valuePairPrefix(name);
myBuilder.append("{");
return new AnnotationTextCollector(null, new AnnotationResultCallback() {
@Override
public void callback(final String text) {
myBuilder.append(text).append('}');
}
});
}
@Override
public void visitEnd() {
if (hasParams && myDesc != null) {
myBuilder.append(')');
}
myCallback.callback(myBuilder.toString());
}
}
private static class AnnotationCollectingVisitor extends FieldVisitor {
private final PsiModifierListStub myModList;
private AnnotationCollectingVisitor(final PsiModifierListStub modList) {
super(ASM_API);
myModList = modList;
}
@Override
public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
return new AnnotationTextCollector(desc, new AnnotationResultCallback() {
@Override
public void callback(final String text) {
new PsiAnnotationStubImpl(myModList, text);
}
});
}
}
private static class AnnotationParamCollectingVisitor extends MethodVisitor {
private final PsiMethodStub myOwner;
private final PsiModifierListStub myModList;
private final int myIgnoreCount;
private final int myParamIgnoreCount;
private final int myParamCount;
private final PsiParameterStubImpl[] myParamStubs;
private int myUsedParamSize = 0;
private int myUsedParamCount = 0;
private AnnotationParamCollectingVisitor(@NotNull PsiMethodStub owner,
@NotNull PsiModifierListStub modList,
final int ignoreCount,
final int paramIgnoreCount,
final int paramCount,
@NotNull PsiParameterStubImpl[] paramStubs) {
super(ASM_API);
myOwner = owner;
myModList = modList;
myIgnoreCount = ignoreCount;
myParamIgnoreCount = paramIgnoreCount;
myParamCount = paramCount;
myParamStubs = paramStubs;
}
@Override
public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
return new AnnotationTextCollector(desc, new AnnotationResultCallback() {
@Override
public void callback(final String text) {
new PsiAnnotationStubImpl(myModList, text);
}
});
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
return new AnnotationTextCollector(null, new AnnotationResultCallback() {
@Override
public void callback(final String text) {
((PsiMethodStubImpl)myOwner).setDefaultValueText(text);
}
});
}
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
if (index >= myIgnoreCount) {
// long and double variables increase the index by 2, not by 1
int paramIndex = (index - myIgnoreCount == myUsedParamSize) ? myUsedParamCount : index - myIgnoreCount;
if (paramIndex >= myParamCount) return;
if (ClsParsingUtil.isJavaIdentifier(name, LanguageLevel.HIGHEST)) {
PsiParameterStubImpl parameterStub = myParamStubs[paramIndex];
if (parameterStub != null) {
parameterStub.setName(name);
}
}
myUsedParamCount = paramIndex+1;
if ("D".equals(desc) || "J".equals(desc)) {
myUsedParamSize += 2;
}
else {
myUsedParamSize++;
}
}
}
@Override
@Nullable
public AnnotationVisitor visitParameterAnnotation(final int parameter, String desc, boolean visible) {
if (parameter < myParamIgnoreCount) {
return null;
}
return new AnnotationTextCollector(desc, new AnnotationResultCallback() {
@Override
public void callback(final String text) {
new PsiAnnotationStubImpl(myOwner.findParameter(parameter - myParamIgnoreCount).getModList(), text);
}
});
}
}
@Nullable
private static String constToString(@Nullable Object value, @Nullable String type, boolean anno) {
if (value == null) return null;
if (value instanceof String) {
return "\"" + StringUtil.escapeStringCharacters((String)value) + "\"";
}
if (value instanceof Boolean || value instanceof Short || value instanceof Byte) {
return value.toString();
}
if (value instanceof Character) {
return "'" + StringUtil.escapeCharCharacters(value.toString()) + "'";
}
if (value instanceof Long) {
return value.toString() + "L";
}
if (value instanceof Integer) {
if ("boolean".equals(type)) {
if (value.equals(0)) return "false";
if (value.equals(1)) return "true";
}
if ("char".equals(type)) {
char ch = (char)((Integer)value).intValue();
return "'" + StringUtil.escapeCharCharacters(String.valueOf(ch)) + "'";
}
return value.toString();
}
if (value instanceof Double) {
final double d = (Double)value;
if (Double.isInfinite(d)) {
return d > 0 ? DOUBLE_POSITIVE_INF : DOUBLE_NEGATIVE_INF;
}
else if (Double.isNaN(d)) {
return DOUBLE_NAN;
}
return Double.toString(d);
}
if (value instanceof Float) {
final float v = (Float)value;
if (Float.isInfinite(v)) {
return v > 0 ? FLOAT_POSITIVE_INF : FLOAT_NEGATIVE_INF;
}
else if (Float.isNaN(v)) {
return FLOAT_NAN;
}
else {
return Float.toString(v) + "f";
}
}
if (value.getClass().isArray()) {
StringBuilder buffer = new StringBuilder();
buffer.append('{');
for (int i = 0, length = Array.getLength(value); i < length; i++) {
if (i > 0) buffer.append(", ");
buffer.append(Array.get(value, i));
}
buffer.append('}');
return buffer.toString();
}
if (anno && value instanceof Type) {
return getTypeText((Type)value) + ".class";
}
return null;
}
private interface AnnotationResultCallback {
void callback(String text);
}
private static String getClassName(final String name) {
return getTypeText(Type.getObjectType(name));
}
@NotNull
private static String getTypeText(@NotNull Type type) {
final String raw = type.getClassName();
// As the '$' char is a valid java identifier and is actively used by byte code generators, the problem is
// which occurrences of this char should be replaced and which should not.
// Heuristic: replace only those $ occurrences that are surrounded non-"$" chars
// (most likely generated by javac to separate inner or anonymous class name)
// Leading and trailing $ chars should be left unchanged.
return raw.indexOf('$') >= 0 ? REGEX_PATTERN.matcher(raw).replaceAll("\\.") : raw;
}
}