/*
* Copyright 2011-present Greg Shrago
*
* 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 org.intellij.grammar.java;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.FakePsiElement;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceProvider;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.*;
import org.jetbrains.org.objectweb.asm.signature.SignatureReader;
import org.jetbrains.org.objectweb.asm.signature.SignatureVisitor;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.lang.reflect.Type;
import java.util.*;
/**
* @author gregsh
*/
public abstract class JavaHelper {
public enum MethodType { STATIC, INSTANCE, CONSTRUCTOR }
public static JavaHelper getJavaHelper(@NotNull PsiElement context) {
PsiFile file = context.getContainingFile();
JavaHelper service = ServiceManager.getService(file.getProject(), JavaHelper.class);
return service == null ? new AsmHelper() : service;
}
@Nullable
public IntentionAction getCreateClassQuickFix(PsiElement context, String className, boolean intf, String superClass) {
return null;
}
public abstract boolean isPublic(@Nullable NavigatablePsiElement element);
@Nullable
public NavigatablePsiElement findClass(@Nullable String className) {
return null;
}
@NotNull
public List<NavigatablePsiElement> findClassMethods(@Nullable String className,
@NotNull MethodType methodType,
@Nullable String methodName,
int paramCount,
String... paramTypes) {
return Collections.emptyList();
}
@Nullable
public String getSuperClassName(@Nullable String className) {
return null;
}
@NotNull
public List<String> getMethodTypes(@Nullable NavigatablePsiElement method) {
return Collections.emptyList();
}
@NotNull
public String getDeclaringClass(@Nullable NavigatablePsiElement method) {
return "";
}
@NotNull
public List<String> getAnnotations(@Nullable NavigatablePsiElement element) {
return Collections.emptyList();
}
@Nullable
public PsiReferenceProvider getClassReferenceProvider() {
return null;
}
@Nullable
public NavigationItem findPackage(@Nullable String packageName) {
return null;
}
private static boolean acceptsName(@Nullable String expected, @Nullable String actual) {
return "*".equals(expected) || expected != null && expected.equals(actual);
}
private static boolean acceptsModifiers(int modifiers, MethodType methodType) {
return !Modifier.isAbstract(modifiers) &&
!(methodType == MethodType.CONSTRUCTOR && Modifier.isPrivate(modifiers));
}
private static class PsiHelper extends AsmHelper {
private final JavaPsiFacade myFacade;
private final PsiElementFactory myElementFactory;
private PsiHelper(JavaPsiFacade facade, PsiElementFactory elementFactory) {
myFacade = facade;
myElementFactory = elementFactory;
}
@Override
public PsiReferenceProvider getClassReferenceProvider() {
JavaClassReferenceProvider provider = new JavaClassReferenceProvider();
provider.setSoft(false);
return provider;
}
@Nullable
@Override
public IntentionAction getCreateClassQuickFix(PsiElement context, String className, boolean intf, String superClass) {
return QuickFixFactory.getInstance().createCreateClassOrInterfaceFix(context, className, !intf, superClass);
}
@Override
public boolean isPublic(@Nullable NavigatablePsiElement element) {
return element instanceof PsiModifierListOwner && ((PsiModifierListOwner)element).hasModifierProperty("public");
}
@Override
public NavigatablePsiElement findClass(String className) {
PsiClass aClass = findClassSafe(className);
return aClass != null ? aClass : super.findClass(className);
}
private PsiClass findClassSafe(String className) {
if (className == null) return null;
try {
return myFacade.findClass(className, GlobalSearchScope.allScope(myFacade.getProject()));
}
catch (IndexNotReadyException e) {
return null;
}
}
@Override
public NavigationItem findPackage(String packageName) {
return myFacade.findPackage(packageName);
}
@NotNull
@Override
public List<NavigatablePsiElement> findClassMethods(@Nullable String className,
@NotNull MethodType methodType,
@Nullable String methodName,
int paramCount,
String... paramTypes) {
if (methodName == null) return Collections.emptyList();
PsiClass aClass = findClassSafe(className);
if (aClass == null) return super.findClassMethods(className, methodType, methodName, paramCount, paramTypes);
List<NavigatablePsiElement> result = ContainerUtil.newArrayList();
PsiMethod[] methods = methodType == MethodType.CONSTRUCTOR ? aClass.getConstructors() : aClass.getMethods();
for (PsiMethod method : methods) {
if (!acceptsName(methodName, method.getName())) continue;
if (!acceptsMethod(method, methodType)) continue;
if (!acceptsMethod(myElementFactory, method, paramCount, paramTypes)) continue;
result.add(method);
}
return result;
}
@Nullable
@Override
public String getSuperClassName(@Nullable String className) {
PsiClass aClass = findClassSafe(className);
PsiClass superClass = aClass != null ? aClass.getSuperClass() : null;
return superClass != null ? superClass.getQualifiedName() : super.getSuperClassName(className);
}
private static boolean acceptsMethod(PsiElementFactory elementFactory,
PsiMethod method,
int paramCount,
String... paramTypes) {
PsiParameterList parameterList = method.getParameterList();
if (paramCount >= 0 && paramCount != parameterList.getParametersCount()) return false;
if (paramTypes.length == 0) return true;
if (parameterList.getParametersCount() < paramTypes.length) return false;
PsiParameter[] psiParameters = parameterList.getParameters();
for (int i = 0; i < paramTypes.length; i++) {
String paramType = paramTypes[i];
PsiParameter parameter = psiParameters[i];
PsiType psiType = parameter.getType();
if (acceptsName(paramType, psiType.getCanonicalText())) continue;
try {
if (psiType.isAssignableFrom(elementFactory.createTypeFromText(paramType, parameter))) continue;
}
catch (IncorrectOperationException ignored) {
}
return false;
}
return true;
}
private static boolean acceptsMethod(PsiMethod method, MethodType methodType) {
PsiModifierList modifierList = method.getModifierList();
return (methodType == MethodType.STATIC) == modifierList.hasModifierProperty(PsiModifier.STATIC) &&
!modifierList.hasModifierProperty(PsiModifier.ABSTRACT) &&
!(methodType == MethodType.CONSTRUCTOR && modifierList.hasModifierProperty(PsiModifier.PROTECTED));
}
@NotNull
@Override
public List<String> getMethodTypes(NavigatablePsiElement method) {
if (!(method instanceof PsiMethod)) return super.getMethodTypes(method);
PsiMethod psiMethod = (PsiMethod)method;
PsiType returnType = psiMethod.getReturnType();
List<String> strings = new ArrayList<>();
strings.add(returnType == null ? "" : returnType.getCanonicalText());
for (PsiParameter parameter : psiMethod.getParameterList().getParameters()) {
PsiType type = parameter.getType();
boolean generic = type instanceof PsiClassType && ((PsiClassType)type).resolve() instanceof PsiTypeParameter;
strings.add((generic ? "<" : "") + type.getCanonicalText(false) + (generic ? ">" : ""));
strings.add(parameter.getName());
}
return strings;
}
@NotNull
@Override
public String getDeclaringClass(@Nullable NavigatablePsiElement method) {
if (!(method instanceof PsiMethod)) return super.getDeclaringClass(method);
PsiMethod psiMethod = (PsiMethod)method;
PsiClass aClass = psiMethod.getContainingClass();
return aClass == null ? "" : StringUtil.notNullize(aClass.getQualifiedName());
}
@NotNull
@Override
public List<String> getAnnotations(NavigatablePsiElement element) {
if (!(element instanceof PsiModifierListOwner)) return super.getAnnotations(element);
PsiModifierList modifierList = ((PsiModifierListOwner)element).getModifierList();
if (modifierList == null) return ContainerUtilRt.emptyList();
List<String> result = new ArrayList<>();
for (PsiAnnotation annotation : modifierList.getAnnotations()) {
if (annotation.getParameterList().getAttributes().length > 0) continue;
ContainerUtil.addIfNotNull(result, annotation.getQualifiedName());
}
return result;
}
}
public static class ReflectionHelper extends JavaHelper {
@Override
public boolean isPublic(@Nullable NavigatablePsiElement element) {
Object delegate = element instanceof MyElement ? ((MyElement)element).delegate : null;
int modifiers = delegate instanceof Class ? ((Class)delegate).getModifiers() :
delegate instanceof Method ? ((Method)delegate).getModifiers() :
0;
return Modifier.isPublic(modifiers);
}
@Nullable
@Override
public NavigatablePsiElement findClass(String className) {
Class<?> aClass = findClassSafe(className);
return aClass == null ? null : new MyElement<Class>(aClass);
}
@Nullable
private static Class<?> findClassSafe(String className) {
if (className == null) return null;
try {
return Class.forName(className);
}
catch (Exception e) {
return null;
}
}
@NotNull
@Override
public List<NavigatablePsiElement> findClassMethods(@Nullable String className,
@NotNull MethodType methodType,
@Nullable String methodName,
int paramCount,
String... paramTypes) {
Class<?> aClass = findClassSafe(className);
if (aClass == null || methodName == null) return Collections.emptyList();
List<NavigatablePsiElement> result = ContainerUtil.newArrayList();
Member[] methods = methodType == MethodType.CONSTRUCTOR ? aClass.getDeclaredConstructors() : aClass.getDeclaredMethods();
for (Member method : methods) {
if (!acceptsName(methodName, method.getName())) continue;
if (!acceptsMethod(method, methodType)) continue;
if (!acceptsMethod(method, paramCount, paramTypes)) continue;
result.add(new MyElement<>(method));
}
return result;
}
@Nullable
@Override
public String getSuperClassName(@Nullable String className) {
Class<?> aClass = findClassSafe(className);
Class<?> superClass = aClass == null ? null : aClass.getSuperclass();
return superClass != null && superClass != Object.class ? superClass.getName() : null;
}
private static boolean acceptsMethod(Member method, int paramCount, String... paramTypes) {
Class<?>[] parameterTypes = method instanceof Method? ((Method)method).getParameterTypes() :
method instanceof Constructor ? ((Constructor)method).getParameterTypes() :
ArrayUtil.EMPTY_CLASS_ARRAY;
if (paramCount >= 0 && paramCount != parameterTypes.length) return false;
if (paramTypes.length == 0) return true;
if (paramTypes.length > parameterTypes.length) return false;
for (int i = 0; i < paramTypes.length; i++) {
String paramType = paramTypes[i];
Class<?> parameter = parameterTypes[i];
if (acceptsName(paramType, parameter.getCanonicalName())) continue;
Class<?> paramClass = findClassSafe(paramType);
if (paramClass != null && parameter.isAssignableFrom(paramClass)) continue;
return false;
}
return true;
}
private static boolean acceptsMethod(Member method, MethodType methodType) {
int modifiers = method.getModifiers();
return (methodType == MethodType.STATIC) == Modifier.isStatic(modifiers) &&
acceptsModifiers(modifiers, methodType);
}
@NotNull
@Override
public List<String> getMethodTypes(NavigatablePsiElement method) {
if (method == null) return Collections.emptyList();
Method delegate = ((MyElement<Method>)method).delegate;
Type[] parameterTypes = delegate.getGenericParameterTypes();
List<String> result = new ArrayList<>(parameterTypes.length + 1);
result.add(delegate.getGenericReturnType().toString());
int paramCounter = 0;
for (Type parameterType : parameterTypes) {
result.add(parameterType.toString());
result.add("p" + (paramCounter++));
}
return result;
}
@NotNull
@Override
public String getDeclaringClass(@Nullable NavigatablePsiElement method) {
if (method == null) return "";
return ((MyElement<Method>)method).delegate.getDeclaringClass().getName();
}
@NotNull
@Override
public List<String> getAnnotations(NavigatablePsiElement element) {
if (element == null) return Collections.emptyList();
AnnotatedElement delegate = ((MyElement<AnnotatedElement>)element).delegate;
Annotation[] annotations = delegate.getDeclaredAnnotations();
List<String> result = new ArrayList<>(annotations.length);
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType(); // todo parameters?
ContainerUtil.addIfNotNull(result, annotationType.getCanonicalName());
}
return result;
}
}
public static class AsmHelper extends JavaHelper {
@Override
public boolean isPublic(@Nullable NavigatablePsiElement element) {
Object delegate = element instanceof MyElement ? ((MyElement)element).delegate : null;
int access = delegate instanceof ClassInfo ? ((ClassInfo)delegate).modifiers :
delegate instanceof MethodInfo ? ((MethodInfo)delegate).modifiers :
0;
return Modifier.isPublic(access);
}
@Nullable
@Override
public NavigatablePsiElement findClass(String className) {
ClassInfo info = findClassSafe(className);
return info == null ? null : new MyElement<>(info);
}
@NotNull
@Override
public List<NavigatablePsiElement> findClassMethods(@Nullable String className,
@NotNull MethodType methodType,
@Nullable final String methodName,
int paramCount,
String... paramTypes) {
ClassInfo aClass = findClassSafe(className);
if (aClass == null || methodName == null) return Collections.emptyList();
List<NavigatablePsiElement> result = ContainerUtil.newArrayList();
for (MethodInfo method : aClass.methods) {
if (!acceptsName(methodName, method.name)) continue;
if (!acceptsMethod(method, methodType)) continue;
if (!acceptsMethod(method, paramCount, paramTypes)) continue;
result.add(new MyElement<>(method));
}
return result;
}
@Nullable
@Override
public String getSuperClassName(@Nullable String className) {
ClassInfo aClass = findClassSafe(className);
return aClass == null ? null : aClass.superClass;
}
private static boolean acceptsMethod(MethodInfo method, int paramCount, String... paramTypes) {
if (paramCount >= 0 && paramCount + 1 != method.types.size()) return false;
if (paramTypes.length == 0) return true;
if (paramTypes.length + 1 > method.types.size()) return false;
for (int i = 0; i < paramTypes.length; i++) {
String paramType = paramTypes[i];
String parameter = method.types.get(i + 1);
if (acceptsName(paramType, parameter)) continue;
ClassInfo info = findClassSafe(paramType);
if (info != null) {
if (Comparing.equal(info.superClass, parameter)) continue;
if (info.interfaces.contains(parameter)) continue;
}
return false;
}
return true;
}
private static boolean acceptsMethod(MethodInfo method, MethodType methodType) {
return method.methodType == methodType && acceptsModifiers(method.modifiers, methodType);
}
@NotNull
@Override
public List<String> getMethodTypes(NavigatablePsiElement method) {
if (method == null) return Collections.emptyList();
MethodInfo signature = ((MyElement<MethodInfo>)method).delegate;
return signature.types;
}
@NotNull
@Override
public String getDeclaringClass(@Nullable NavigatablePsiElement method) {
if (method == null) return "";
return ((MyElement<MethodInfo>)method).delegate.declaringClass;
}
@NotNull
@Override
public List<String> getAnnotations(NavigatablePsiElement element) {
Object delegate = element == null ? null : ((MyElement<?>)element).delegate;
if (delegate instanceof ClassInfo) return ((ClassInfo)delegate).annotations;
if (delegate instanceof MethodInfo) return ((MethodInfo)delegate).annotations;
return Collections.emptyList();
}
private static ClassInfo findClassSafe(String className) {
if (className == null) return null;
try {
int lastDot = className.length();
InputStream is;
do {
String s = className.substring(0, lastDot).replace('.', '/') +
className.substring(lastDot).replace('.', '$') +
".class";
is = JavaHelper.class.getClassLoader().getResourceAsStream(s);
lastDot = className.lastIndexOf('.', lastDot - 1);
}
while(is == null && lastDot > 0);
if (is == null) return null;
byte[] bytes = FileUtil.loadBytes(is);
is.close();
return getClassInfo(className, bytes);
}
catch (Exception e) {
reportException(e, className, null);
}
return null;
}
private static ClassInfo getClassInfo(String className, byte[] bytes) {
final ClassInfo info = new ClassInfo();
info.name = className;
new ClassReader(bytes).accept(new MyClassVisitor(info), 0);
return info;
}
private static MethodInfo getMethodInfo(String className, String methodName, String signature) {
final MethodInfo methodInfo = new MethodInfo();
methodInfo.name = methodName;
methodInfo.declaringClass = className;
try {
MySignatureVisitor visitor = new MySignatureVisitor(methodInfo);
new SignatureReader(signature).accept(visitor);
visitor.finishElement(null);
}
catch (Exception e) {
reportException(e, className + "#" + methodName + "()", signature);
}
return methodInfo;
}
private static void reportException(Exception e, String target, String signature) {
//noinspection UseOfSystemOutOrSystemErr
System.err.println(e.getClass().getSimpleName() + " while reading " + target +
(signature == null ? "" : " signature " + signature));
}
private static class MyClassVisitor extends ClassVisitor {
private final ClassInfo myInfo;
MyClassVisitor(ClassInfo info) {
super(Opcodes.ASM5);
myInfo = info;
}
public void visit(int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
myInfo.modifiers = access;
myInfo.superClass = fixClassName(superName);
for (String s : interfaces) {
myInfo.interfaces.add(fixClassName(s));
}
if (signature != null) {
new SignatureReader(signature).accept(new SignatureVisitor(Opcodes.ASM5) {
@Override
public void visitFormalTypeParameter(String name) {
myInfo.typeParameters.add(name);
}
});
}
}
@Override
public void visitEnd() {
}
@Override
public MethodVisitor visitMethod(int access,
String name,
String desc,
String signature,
String[] exceptions) {
final MethodInfo m = getMethodInfo(myInfo.name, name, ObjectUtils.chooseNotNull(signature, desc));
m.modifiers = access;
m.methodType = "<init>".equals(name)? MethodType.CONSTRUCTOR :
Modifier.isStatic(access) ? MethodType.STATIC :
MethodType.INSTANCE;
myInfo.methods.add(m);
return new MethodVisitor(Opcodes.ASM5) {
@Override
public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
return new MyAnnotationVisitor() {
@Override
public void visitEnd() {
if (annoParamCounter == 0) {
m.annotations.add(fixClassName(desc.substring(1, desc.length() - 1)));
}
}
};
}
};
}
class MyAnnotationVisitor extends AnnotationVisitor {
int annoParamCounter;
MyAnnotationVisitor() {
super(Opcodes.ASM5);
}
@Override
public void visit(String s, Object o) {
annoParamCounter++;
}
@Override
public void visitEnum(String s, String s2, String s3) {
annoParamCounter++;
}
@Override
public AnnotationVisitor visitAnnotation(String s, String s2) {
annoParamCounter++;
return null;
}
@Override
public AnnotationVisitor visitArray(String s) {
annoParamCounter++;
return null;
}
}
}
private static String fixClassName(String s) {
return s == null ? null : s.replace('/', '.').replace('$', '.');
}
private static class MySignatureVisitor extends SignatureVisitor {
enum State {PARAM, RETURN, CLASS, ARRAY, GENERIC, BOUNDS, EXCEPTION}
final MethodInfo methodInfo;
final Deque<State> states = new ArrayDeque<>();
/** @noinspection StringBufferField*/
final StringBuilder sb = new StringBuilder();
MySignatureVisitor(MethodInfo methodInfo) {
super(Opcodes.ASM5);
this.methodInfo = methodInfo;
}
@Override
public void visitFormalTypeParameter(String s) {
// collect them
}
@Override
public SignatureVisitor visitInterfaceBound() {
finishElement(null);
states.push(State.BOUNDS);
return this;
}
@Override
public SignatureVisitor visitSuperclass() {
finishElement(null);
states.push(State.BOUNDS);
return this;
}
@Override
public SignatureVisitor visitInterface() {
finishElement(null);
states.push(State.BOUNDS);
return this;
}
@Override
public SignatureVisitor visitParameterType() {
finishElement(null);
states.push(State.PARAM);
return this;
}
@Override
public SignatureVisitor visitReturnType() {
finishElement(null);
states.push(State.RETURN);
return this;
}
@Override
public SignatureVisitor visitExceptionType() {
finishElement(null);
states.push(State.EXCEPTION);
return this;
}
@Override
public void visitBaseType(char c) {
sb.append(org.jetbrains.org.objectweb.asm.Type.getType(String.valueOf(c)).getClassName());
}
@Override
public void visitTypeVariable(String s) {
sb.append("<").append(s).append(">");
}
@Override
public SignatureVisitor visitArrayType() {
states.push(State.ARRAY);
return this;
}
@Override
public void visitClassType(String s) {
states.push(State.CLASS);
sb.append(fixClassName(s));
}
@Override
public void visitInnerClassType(String s) {
}
@Override
public void visitTypeArgument() {
states.push(State.GENERIC);
sb.append("<");
}
@Override
public SignatureVisitor visitTypeArgument(char c) {
if (states.peekFirst() == State.CLASS) {
states.push(State.GENERIC);
sb.append("<");
}
else {
finishElement(State.GENERIC);
sb.append(", ");
}
return this;
}
@Override
public void visitEnd() {
finishElement(State.CLASS);
states.pop();
}
private void finishElement(State finishState) {
if (sb.length() == 0) return;
main:
while (!states.isEmpty()) {
if (finishState == states.peekFirst()) break;
State state = states.pop();
switch (state) {
case PARAM:
methodInfo.types.add(sb.toString());
methodInfo.types.add("p" + (methodInfo.types.size() / 2));
sb.setLength(0);
break main;
case RETURN:
methodInfo.types.add(0, sb.toString());
sb.setLength(0);
break main;
case ARRAY:
sb.append("[]");
break;
case GENERIC:
sb.append(">");
break;
case CLASS:
case BOUNDS:
break;
}
}
}
}
}
private static class MyElement<T> extends FakePsiElement implements NavigatablePsiElement {
final T delegate;
MyElement(T delegate) {
this.delegate = delegate;
}
@Override
public PsiElement getParent() {
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyElement element = (MyElement)o;
if (!delegate.equals(element.delegate)) return false;
return true;
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public String toString() {
return delegate.toString();
}
}
private static class ClassInfo {
String name;
String superClass;
int modifiers;
List<String> typeParameters= ContainerUtil.newSmartList();
List<String> interfaces = ContainerUtil.newSmartList();
List<String> annotations = ContainerUtil.newSmartList();
List<MethodInfo> methods = ContainerUtil.newSmartList();
}
private static class MethodInfo {
MethodType methodType;
String name;
String declaringClass;
int modifiers;
List<String> annotations = ContainerUtil.newSmartList();
List<String> types = ContainerUtil.newSmartList();
@Override
public String toString() {
return "MethodInfo" +
"{" + name + types +
", @" + annotations +
'}';
}
}
}