Java tutorial
/******************************************************************************* * Copyright (c) 2018, 2019 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.tooling.jdt.ls.commons.java; import java.net.MalformedURLException; import java.net.URI; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import org.eclipse.core.runtime.Assert; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMemberValuePair; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IParent; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.springframework.ide.vscode.commons.protocol.java.Classpath.CPE; import org.springframework.ide.vscode.commons.protocol.java.JavaElementData; import org.springframework.ide.vscode.commons.protocol.java.JavaTypeData; import org.springframework.ide.vscode.commons.protocol.java.JavaTypeData.JavaTypeKind; import org.springframework.ide.vscode.commons.protocol.java.MemberData; import org.springframework.ide.vscode.commons.protocol.java.TypeData; import org.springframework.ide.vscode.commons.protocol.java.TypeData.AnnotationData; import org.springframework.ide.vscode.commons.protocol.java.TypeData.ClasspathEntryData; import org.springframework.ide.vscode.commons.protocol.java.TypeData.FieldData; import org.springframework.ide.vscode.commons.protocol.java.TypeData.MethodData; import org.springframework.ide.vscode.commons.protocol.java.TypeDescriptorData; import org.springframework.tooling.jdt.ls.commons.Logger; import org.springframework.tooling.jdt.ls.commons.classpath.ClasspathUtil; import org.springframework.tooling.jdt.ls.commons.javadoc.JavadocUtils; import org.springframework.tooling.jdt.ls.commons.resources.ResourceUtils; import com.google.common.collect.ImmutableList; public class JavaData { private Logger logger; private Function<IJavaElement, String> labelProvider; public JavaData(Function<IJavaElement, String> labelProvider, Logger logger) { this.labelProvider = labelProvider; this.logger = logger; } public TypeData typeData(String projectUri, String bindingKey, boolean lookInOtherProjects) { try { IJavaElement element = findElement(projectUri == null ? null : URI.create(projectUri), bindingKey, lookInOtherProjects); if (element instanceof IType) { return createTypeData((IType) element); } } catch (Exception e) { logger.log(e); } return null; } public static IJavaElement findElement(URI projectUri, String bindingKey, boolean lookInOtherProjects) throws Exception { IJavaProject javaProject = projectUri == null ? null : ResourceUtils.getJavaProject(projectUri); IJavaElement element = javaProject == null ? null : findElement(javaProject, bindingKey); if (lookInOtherProjects && element == null) { for (IJavaProject jp : ResourceUtils.allJavaProjects()) { if (jp != javaProject) { element = findElement(jp, bindingKey); if (element != null) { break; } } } } return element; } private static IJavaElement findElement(IJavaProject project, String bindingKey) { IJavaElement element = null; // JDT cannot find anonymous inner type from its binding key // Find its declaring type. If declaring type found then anonymous inner type is present in the binding key String declaringTypeFromAnonymousInnerType = delcaringTypeOfAnonymousInnerType(bindingKey); try { if (declaringTypeFromAnonymousInnerType == null) { element = project.findElement(bindingKey, null); } else { // Look for element inside the enclosing type that JDT can find. Brute force finding algorithm element = findInnerElement(project.findElement(declaringTypeFromAnonymousInnerType, null), bindingKey); } } catch (Throwable t) { // ignore } if (element == null) { // Try modifying the binding key to search for the alternate binding try { String alternateBinding = JavadocUtils.alternateBinding(bindingKey); if (alternateBinding != null) { element = project.findElement(alternateBinding, null); } } catch (Throwable t) { // ignore } } return element; } private static IJavaElement findInnerElement(IJavaElement container, String bindingKey) { if (container instanceof IField) { if (bindingKey.equals(((IField) container).getKey())) { return container; } } else if (container instanceof IMethod) { if (bindingKey.equals(((IMethod) container).getKey())) { return container; } } else if (container instanceof IType) { if (bindingKey.equals(((IType) container).getKey())) { return container; } } if (container instanceof IParent) { try { for (IJavaElement e : ((IParent) container).getChildren()) { IJavaElement found = findInnerElement(e, bindingKey); if (found != null) { return found; } } } catch (JavaModelException e) { // ignore } } return null; } private static String delcaringTypeOfAnonymousInnerType(String bindingKey) { int idx = bindingKey.indexOf('$'); if (idx >= 0) { String rest = bindingKey.substring(idx + 1); if (rest.charAt(rest.length() - 1) == ';') { rest = rest.substring(0, rest.length() - 1); } String[] tokens = rest.split("$"); for (String token : tokens) { int dotIdx = token.indexOf('.'); String typeToken = dotIdx < 0 ? token : token.substring(0, dotIdx); if (!typeToken.isEmpty()) { try { Integer.parseInt(typeToken); // Succeeded. There is inner anonymous type present. // Return it's enclosing type binding key which JDT should have no problem finding the element for int declaringTypeIdx = bindingKey.indexOf('$' + token); if (declaringTypeIdx < 0) { // Should not happen. Throw exception? return null; } else { return bindingKey.substring(0, declaringTypeIdx) + ';'; } } catch (NumberFormatException e) { // ignore and continue looping } } } } return null; } private void fillJavaElementData(IJavaElement element, JavaElementData data) { data.setName(element.getElementName()); data.setHandleIdentifier(element.getHandleIdentifier()); if (labelProvider != null) { data.setLabel(labelProvider.apply(element)); } } private FieldData createFieldData(IType type, IField field) { FieldData data = new FieldData(); fillMemberData(field, data); data.setBindingKey(field.getKey()); ImmutableList.Builder<AnnotationData> annotationsBuilder = ImmutableList.builder(); try { for (IAnnotation annotation : field.getAnnotations()) { annotationsBuilder.add(createAnnotationData(type, annotation)); } data.setType(createFromSignature(type, field.getTypeSignature())); data.setEnumConstant(field.isEnumConstant()); } catch (JavaModelException e) { logger.log(e); } data.setAnnotations(annotationsBuilder.build()); return data; } private void fillMemberData(IMember member, MemberData data) { fillJavaElementData(member, data); IType declaringType = member.getDeclaringType(); if (declaringType != null) { data.setDeclaringType(declaringType.getKey()); } try { data.setFlags(member.getFlags()); } catch (JavaModelException e) { logger.log(e); } } private MethodData createMethodData(IType type, IMethod method) { MethodData data = new MethodData(); fillMemberData(method, data); data.setBindingKey(method.getKey()); ImmutableList.Builder<AnnotationData> annotationsBuilder = ImmutableList.builder(); ImmutableList.Builder<JavaTypeData> parametersBuilder = ImmutableList.builder(); try { data.setConstructor(method.isConstructor()); data.setReturnType(createFromSignature(type, method.getReturnType())); // Replace 'V' return type at the end with resolved return type if (data.getBindingKey().endsWith(")V") && !"V".equals(data.getReturnType().getName())) { String key = data.getBindingKey(); data.setBindingKey(key.substring(0, key.length() - 1) + data.getReturnType().getName()); } for (IAnnotation annotation : method.getAnnotations()) { annotationsBuilder.add(createAnnotationData(type, annotation)); } for (String parameter : method.getParameterTypes()) { JavaTypeData parameterData = createFromSignature(type, parameter); parametersBuilder.add(parameterData); // Replace unresolved parameters with resolved ones in the binding data.setBindingKey(data.getBindingKey().replace(parameter, parameterData.getName())); } } catch (JavaModelException e) { logger.log(e); } data.setAnnotations(annotationsBuilder.build()); data.setParameters(parametersBuilder.build()); return data; } public TypeData createTypeData(IType type) { TypeData data = new TypeData(); fillTypeData(type, data); return data; } public TypeDescriptorData createTypeDescriptorData(IType type) { TypeDescriptorData data = new TypeDescriptorData(); fillTypeDescriptorData(type, data); return data; } private void fillTypeDescriptorData(IType type, TypeDescriptorData data) { fillMemberData(type, data); data.setFqName(type.getFullyQualifiedName()); try { data.setAnnotation(type.isAnnotation()); data.setClass(type.isClass()); data.setEnum(type.isEnum()); data.setInterface(type.isInterface()); data.setSuperClassName(type.getSuperclassName()); data.setSuperInterfaceNames(type.getSuperInterfaceNames()); } catch (JavaModelException e) { logger.log(e); } } private void fillTypeData(IType type, TypeData data) { fillTypeDescriptorData(type, data); data.setBindingKey(type.getKey()); ImmutableList.Builder<FieldData> fieldsBuilder = ImmutableList.builder(); ImmutableList.Builder<MethodData> methodsBuilder = ImmutableList.builder(); ImmutableList.Builder<AnnotationData> annotationsBuilder = ImmutableList.builder(); try { for (IField field : type.getFields()) { fieldsBuilder.add(createFieldData(type, field)); } for (IMethod method : type.getMethods()) { methodsBuilder.add(createMethodData(type, method)); } for (IAnnotation annotation : type.getAnnotations()) { annotationsBuilder.add(createAnnotationData(type, annotation)); } } catch (JavaModelException e) { logger.log(e); } data.setFields(fieldsBuilder.build()); data.setMethods(methodsBuilder.build()); data.setAnnotations(annotationsBuilder.build()); data.setClasspathEntry(createClasspathEntryData(type)); } private ClasspathEntryData createClasspathEntryData(IMember member) { ClasspathEntryData data = new ClasspathEntryData(); IPackageFragmentRoot packageFragmentRoot = (IPackageFragmentRoot) member .getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); if (packageFragmentRoot != null) { try { IClasspathEntry entry = packageFragmentRoot.getResolvedClasspathEntry(); if (entry != null) { List<CPE> cpes = ClasspathUtil.createCpes(packageFragmentRoot.getJavaProject(), entry); Assert.isTrue(cpes.size() < 2); if (!cpes.isEmpty()) { data.setCpe(cpes.get(0)); } } } catch (JavaModelException | MalformedURLException e) { logger.log(e); } } ITypeRoot typeRoot = member.getTypeRoot(); try { if (typeRoot != null && typeRoot.getModule() != null) { data.setModule(typeRoot.getModule().getElementName()); } } catch (JavaModelException e) { logger.log(e); } return data; } private AnnotationData createAnnotationData(IType type, IAnnotation annotation) { AnnotationData data = new AnnotationData(); fillAnnotationData(type, annotation, data); return data; } private void fillAnnotationData(IType type, IAnnotation annotation, AnnotationData data) { fillJavaElementData(annotation, data); Map<String, Object> pairs = new HashMap<>(); try { data.setFqName(resolveFQName(type, annotation.getElementName())); for (IMemberValuePair pair : annotation.getMemberValuePairs()) { pairs.put(pair.getMemberName(), pair.getValue()); } } catch (JavaModelException e) { logger.log(e); } data.setValuePairs(pairs); } private JavaTypeData createFromSignature(IType type, String signature) { JavaTypeData data = new JavaTypeData(); data.setName(signature); char[] typeSignature = signature.toCharArray(); String[] typeArguments = Signature.getTypeArguments(signature); if (typeArguments != null && typeArguments.length > 0) { JavaTypeData[] javaTypeArguments = new JavaTypeData[typeArguments.length]; for (int i = 0; i < typeArguments.length; i++) { javaTypeArguments[i] = createFromSignature(type, typeArguments[i]); // In case binding key is unresolved replace each argument with resolved one. data.setName(data.getName().replace(typeArguments[i], javaTypeArguments[i].getName())); } data.setKind(JavaTypeKind.PARAMETERIZED); LinkedHashMap<String, Object> extras = new LinkedHashMap<>(); String typeErasure = Signature.getTypeErasure(signature); JavaTypeData owner = createFromSignature(type, typeErasure); extras.put("owner", owner); extras.put("arguments", javaTypeArguments); data.setExtras(extras); // In case binding key is unresolved replace owner. Trim trailing ; from type erasure string and from the replacement data.setName(data.getName().replace(typeErasure.substring(0, typeErasure.length() - 1), owner.getName().substring(0, owner.getName().length() - 1))); } else { // need a minimum 1 char if (typeSignature.length < 1) { throw new IllegalArgumentException(); } char c = typeSignature[0]; if (c == Signature.C_GENERIC_START) { int count = 1; for (int i = 1, length = typeSignature.length; i < length; i++) { switch (typeSignature[i]) { case Signature.C_GENERIC_START: count++; break; case Signature.C_GENERIC_END: count--; break; } if (count == 0) { if (i + 1 < length) c = typeSignature[i + 1]; break; } } } switch (c) { case Signature.C_ARRAY: data.setKind(JavaTypeKind.ARRAY); LinkedHashMap<String, Object> extras = new LinkedHashMap<>(); extras.put("component", createFromSignature(type, Signature.getElementType(signature))); extras.put("dimensions", Signature.getArrayCount(typeSignature)); data.setExtras(extras); break; case Signature.C_RESOLVED: data.setKind(JavaTypeKind.CLASS); break; case Signature.C_TYPE_VARIABLE: data.setKind(JavaTypeKind.TYPE_VARIABLE); break; case Signature.C_BOOLEAN: data.setKind(JavaTypeKind.BOOLEAN); break; case Signature.C_BYTE: data.setKind(JavaTypeKind.BYTE); break; case Signature.C_CHAR: data.setKind(JavaTypeKind.CHAR); break; case Signature.C_DOUBLE: data.setKind(JavaTypeKind.DOUBLE); break; case Signature.C_FLOAT: data.setKind(JavaTypeKind.FLOAT); break; case Signature.C_INT: data.setKind(JavaTypeKind.INT); break; case Signature.C_LONG: data.setKind(JavaTypeKind.LONG); break; case Signature.C_SHORT: data.setKind(JavaTypeKind.SHORT); break; case Signature.C_VOID: data.setKind(JavaTypeKind.VOID); break; case Signature.C_STAR: case Signature.C_SUPER: case Signature.C_EXTENDS: data.setKind(JavaTypeKind.WILDCARD); break; case Signature.C_UNRESOLVED: if (type != null) { // Attempt to resolve type. For some reason JDT has them unresolved for type members try { String signatureSimpleName = Signature.getSignatureSimpleName(signature); String resolvedType = resolveFQName(type, signatureSimpleName); if (resolvedType != null) { data.setKind(JavaTypeKind.CLASS); data.setName("L" + resolvedType.replace('.', '/') + ";"); break; } } catch (JavaModelException e) { data.setKind(JavaTypeKind.UNRESOLVED); } } case Signature.C_CAPTURE: case Signature.C_INTERSECTION: case Signature.C_UNION: default: data.setKind(JavaTypeKind.UNRESOLVED); break; } } return data; } private String resolveFQName(IType type, String name) throws JavaModelException { String[][] resolution = type.resolveType(name); if (resolution != null && resolution.length > 0 && resolution[0].length > 1) { return resolution[0][0] + "." + resolution[0][1].replace('.', '$'); } return name; } }