package com.redhat.ceylon.eclipse.core.model; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.isInCeylonClassesOutputFolder; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.modelJ2C; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.LinkedList; import java.util.Map; import org.eclipse.core.internal.jobs.InternalJob; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; 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.eclipse.jdt.core.WorkingCopyOwner; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.env.AccessRestriction; import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation; import org.eclipse.jdt.internal.compiler.env.IBinaryField; import org.eclipse.jdt.internal.compiler.env.IBinaryMethod; import org.eclipse.jdt.internal.compiler.env.IBinaryNestedType; import org.eclipse.jdt.internal.compiler.env.IBinaryType; import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation; import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; import org.eclipse.jdt.internal.compiler.env.ISourceType; import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.core.BasicCompilationUnit; import org.eclipse.jdt.internal.core.BinaryType; import org.eclipse.jdt.internal.core.JavaElement; import org.eclipse.jdt.internal.core.JavaElementRequestor; import org.eclipse.jdt.internal.core.JavaModelManager; import org.eclipse.jdt.internal.core.JavaProject; import org.eclipse.jdt.internal.core.NameLookup; import org.eclipse.jdt.internal.core.SearchableEnvironment; import org.eclipse.jdt.internal.core.SourceType; import org.eclipse.jdt.internal.core.SourceTypeElementInfo; import org.eclipse.jdt.internal.eval.CodeSnippetSkeleton; import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit; import com.redhat.ceylon.compiler.typechecker.io.VirtualFile; import com.redhat.ceylon.eclipse.core.builder.CeylonNature; import com.redhat.ceylon.ide.common.model.IdeModelLoader; import com.redhat.ceylon.ide.common.model.mirror.SourceDeclarationHolder; import com.redhat.ceylon.ide.common.vfs.FileVirtualFile; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; public class ModelLoaderNameEnvironment extends SearchableEnvironment { public ModelLoaderNameEnvironment(IJavaProject javaProject) throws JavaModelException { super((JavaProject)javaProject, (WorkingCopyOwner) null); } public IJavaProject getJavaProject() { return project; } public IType findTypeInNameLookup(char[][] compoundTypeName) { if (compoundTypeName == null) return null; int length = compoundTypeName.length; if (length <= 1) { if (length == 0) return null; return findTypeInNameLookup(new String(compoundTypeName[0]), IPackageFragment.DEFAULT_PACKAGE_NAME); } int lengthM1 = length - 1; char[][] packageName = new char[lengthM1][]; System.arraycopy(compoundTypeName, 0, packageName, 0, lengthM1); return findTypeInNameLookup( new String(compoundTypeName[lengthM1]), CharOperation.toString(packageName)); } private Method getProgressMonitorMethod = null; private IProgressMonitor getProgressMonitor(Job job) { if (job==null) { return new NullProgressMonitor(); } try { if (getProgressMonitorMethod == null) { for (Method m : InternalJob.class.getDeclaredMethods()) { if ("getProgressMonitor".equals(m.getName())) { getProgressMonitorMethod = m; getProgressMonitorMethod.setAccessible(true); break; } } } Object o = getProgressMonitorMethod.invoke(job); if (o instanceof IProgressMonitor) { return (IProgressMonitor) o; } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; } private boolean endsWith(char [] charArray, char[][] suffixes) { int arrayLength = charArray.length; for (char[] suffix : suffixes) { int suffixLength = suffix.length; if (arrayLength >= suffixLength) { if (CharOperation.fragmentEquals(suffix, charArray, arrayLength - suffixLength, false)) { return true; } } } return false; } public IType findTypeInNameLookup(String typeName, String packageName) { JavaElementRequestor packageRequestor = new JavaElementRequestor(); nameLookup.seekPackageFragments(packageName, false, packageRequestor); LinkedList<IPackageFragment> packagesToSearchIn = new LinkedList<>(); for (IPackageFragment pf : packageRequestor.getPackageFragments()) { IPackageFragmentRoot packageRoot = (IPackageFragmentRoot) pf.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); try { IJavaProject packageProject = packageRoot.getJavaProject(); if (packageProject != null && !CeylonNature.isEnabled(packageProject.getProject())) { continue; } if (packageRoot.getKind() == IPackageFragmentRoot.K_SOURCE) { packagesToSearchIn.addFirst(pf); continue; } if (isInCeylonClassesOutputFolder(packageRoot.getPath())) { continue; } packagesToSearchIn.addLast(pf); } catch (JavaModelException e) { // TODO Auto-generated catch block e.printStackTrace(); } } for (IPackageFragment pf : packagesToSearchIn) { // We use considerSecondTypes = false because we will do it explicitly afterwards, in order to use waitForIndexes=true IType type = nameLookup.findType(typeName, pf, false, NameLookup.ACCEPT_ALL); if (type != null) { return type; } } char[] typeNameCharArray = typeName.toCharArray(); if (CharOperation.equals(TypeConstants.PACKAGE_INFO_NAME, typeNameCharArray) || CharOperation.equals(LookupEnvironmentUtilities.packageDescriptorName, typeNameCharArray) || CharOperation.equals(LookupEnvironmentUtilities.moduleDescriptorName, typeNameCharArray) || CharOperation.equals(LookupEnvironmentUtilities.oldPackageDescriptorName, typeNameCharArray) || CharOperation.equals(LookupEnvironmentUtilities.oldModuleDescriptorName, typeNameCharArray) || endsWith(typeNameCharArray, LookupEnvironmentUtilities.descriptorClassNames)) { // Don't search for secondary types whose name ends with is a quoted of unquoted descriptors // or ends with a quoted descriptor (in case it would be searching for an inner class) return null; } if (LookupEnvironmentUtilities.isSettingInterfaceCompanionClass() && typeName.endsWith("$impl")) { // Don't search for Ceylon interface companion classes in Java Secondary types. return null; } Job currentJob = Job.getJobManager().currentJob(); IProgressMonitor currentMonitor = getProgressMonitor(currentJob); for (IPackageFragment pf : packagesToSearchIn) { IType type = findSecondaryType(typeName, packageName, pf, currentMonitor); if (type != null) { return type; } } return null; } // This is a Copy / Paste from : // org.eclipse.jdt.internal.core.NameLookup.findSecondaryType(...), in order to be able to call it with waitForIndexes = true: // type = nameLookup.findSecondaryType(pf.getElementName(), typeName, pf.getJavaProject(), true, null); // // However the copied method has been changed to adapt it to the model loader needs. private IType findSecondaryType(String typeName, String packageName, IPackageFragment pf, IProgressMonitor currentMonitor) { JavaModelManager manager = JavaModelManager.getJavaModelManager(); try { IJavaProject javaProject = pf.getJavaProject(); @SuppressWarnings("rawtypes") Map secondaryTypePaths = manager.secondaryTypes(javaProject, true, currentMonitor); if (secondaryTypePaths.size() > 0) { @SuppressWarnings("rawtypes") Map types = (Map) secondaryTypePaths.get(packageName==null?"":packageName); //$NON-NLS-1$ if (types != null && types.size() > 0) { boolean startsWithDollar = false; if(typeName.startsWith("$")) { startsWithDollar = true; typeName = typeName.substring(1); } String[] parts = typeName.split("(\\.|\\$)"); if (startsWithDollar) { parts[0] = "$" + parts[0]; } int index = 0; String topLevelClassName = parts[index++]; IType currentClass = (IType) types.get(topLevelClassName); IType result = currentClass; while (index < parts.length) { result = null; String nestedClassName = parts[index++]; if (currentClass != null && currentClass.exists()) { currentClass = currentClass.getType(nestedClassName); result = currentClass; } else { break; } } return result; } } } catch (JavaModelException jme) { // give up } return null; } private static Constructor<NameEnvironmentAnswer> nameEnvironmentAnswerFromSoureTypesConstructor = null; private static boolean hasAdditionalStringParameter = false; private static void loadNameEnvironmentAnswerFromSoureTypesConstructor() throws NoSuchMethodException, SecurityException { if (nameEnvironmentAnswerFromSoureTypesConstructor == null) { Constructor<NameEnvironmentAnswer> c = null; try { c = NameEnvironmentAnswer.class.getConstructor((new ISourceType[0]).getClass(), AccessRestriction.class); } catch (NoSuchMethodException e) { c = NameEnvironmentAnswer.class.getConstructor((new ISourceType[0]).getClass(), AccessRestriction.class, String.class); hasAdditionalStringParameter = true; } c.setAccessible(true); nameEnvironmentAnswerFromSoureTypesConstructor = c; } } private static NameEnvironmentAnswer createNameEnvironmentAnswerFromSoureTypes(ISourceType[] sourceTypes) { try { loadNameEnvironmentAnswerFromSoureTypesConstructor(); if (hasAdditionalStringParameter) { return nameEnvironmentAnswerFromSoureTypesConstructor.newInstance(sourceTypes, null, null); } else { return nameEnvironmentAnswerFromSoureTypesConstructor.newInstance(sourceTypes, null); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | InstantiationException e) { e.printStackTrace(); return null; } } @SuppressWarnings("unchecked") @Override protected NameEnvironmentAnswer find(String typeName, String packageName) { if (packageName == null) packageName = IPackageFragment.DEFAULT_PACKAGE_NAME; if (this.owner != null) { String source = this.owner.findSource(typeName, packageName); if (source != null) { ICompilationUnit cu = new BasicCompilationUnit(source.toCharArray(), CharOperation.splitOn('.', packageName.toCharArray()), typeName + org.eclipse.jdt.internal.core.util.Util.defaultJavaExtension()); return new NameEnvironmentAnswer(cu, null); } } IType type = findTypeInNameLookup(typeName, packageName); if (type != null) { // construct name env answer if (type instanceof BinaryType) { // BinaryType try { return new NameEnvironmentAnswer((IBinaryType) ((BinaryType) type).getElementInfo(), null); } catch (JavaModelException npe) { // fall back to using owner } } else { //SourceType try { // retrieve the requested type SourceTypeElementInfo sourceType = (SourceTypeElementInfo)((SourceType) type).getElementInfo(); ISourceType topLevelType = sourceType; while (topLevelType.getEnclosingType() != null) { topLevelType = topLevelType.getEnclosingType(); } // find all siblings (other types declared in same unit, since may be used for name resolution) IType[] types = sourceType.getHandle().getCompilationUnit().getTypes(); ISourceType[] sourceTypes = new ISourceType[types.length]; // in the resulting collection, ensure the requested type is the first one sourceTypes[0] = sourceType; int length = types.length; for (int i = 0, index = 1; i < length; i++) { ISourceType otherType = (ISourceType) ((JavaElement) types[i]).getElementInfo(); if (!otherType.equals(topLevelType) && index < length) // check that the index is in bounds (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=62861) sourceTypes[index++] = otherType; } return createNameEnvironmentAnswerFromSoureTypes(sourceTypes); } catch (JavaModelException jme) { if (jme.isDoesNotExist() && String.valueOf(TypeConstants.PACKAGE_INFO_NAME).equals(typeName)) { // in case of package-info.java the type doesn't exist in the model, // but the CU may still help in order to fetch package level annotations. return new NameEnvironmentAnswer((ICompilationUnit)type.getParent(), null); } // no usable answer } } } return null; } }