/******************************************************************************* * Copyright (c) 2014 Willink Transformations and others. * 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 * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * E.D.Willink - initial API and implementation *******************************************************************************/ package org.eclipse.ocl.xtext.oclstdlib.scoping; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IParent; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.ocl.pivot.utilities.ClassUtil; import org.eclipse.ocl.xtext.base.scoping.AbstractJavaClassScope; import org.eclipse.ocl.xtext.base.utilities.BaseCSResource; import org.eclipse.ocl.xtext.oclstdlibcs.JavaClassCS; import org.eclipse.ocl.xtext.oclstdlibcs.JavaImplementationCS; import org.eclipse.ocl.xtext.oclstdlibcs.OCLstdlibCSFactory; import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.resource.EObjectDescription; import org.eclipse.xtext.resource.IEObjectDescription; import org.osgi.framework.Bundle; import org.osgi.framework.BundleReference; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; /** * A JavaClassScope supports lookup of Java class names from the OCLstdlib editor. Names are resolved against * a local cache. All names for completion assist are resolved against the full classpath. *<p> * This provides much less functionality that the Xtext JdtBased/ClasspathBased TypeScopes, but much less is * all that is needed. */ public class JavaClassScope extends AbstractJavaClassScope { public static boolean SUPPRESS_WORK_THREAD = false; // Set true to avoid WorkerThread delay when testing public static @NonNull JavaClassScope getAdapter(@NonNull BaseCSResource csResource, @NonNull ClassLoader classLoader) { AbstractJavaClassScope adapter = ClassUtil.getAdapter(AbstractJavaClassScope.class, csResource); if (adapter == null) { adapter = new JavaClassScope(classLoader); csResource.eAdapters().add(adapter); } return (JavaClassScope) adapter; } public static @NonNull JavaClassScope getAdapter(@NonNull BaseCSResource csResource, @NonNull List<@NonNull ClassLoader> classLoaders) { AbstractJavaClassScope adapter = ClassUtil.getAdapter(AbstractJavaClassScope.class, csResource); if (adapter == null) { adapter = new JavaClassScope(classLoaders); csResource.eAdapters().add(adapter); } return (JavaClassScope) adapter; } public static @NonNull JavaClassScope getAdapter(@NonNull BaseCSResource csResource, @NonNull IProject project) { AbstractJavaClassScope adapter = ClassUtil.getAdapter(AbstractJavaClassScope.class, csResource); if (adapter == null) { adapter = new JavaClassScope(project); csResource.eAdapters().add(adapter); } return (JavaClassScope) adapter; } /** * ClassLoaders to help resolve references. */ private final @NonNull List<@NonNull ClassLoader> classLoaders = new ArrayList<@NonNull ClassLoader>(); /** * IProject to help resolve references in an Eclipse context. */ private final @Nullable IProject project; /** * Map from known class names to their allocated EObjects. */ private final @NonNull Map<@NonNull String, @NonNull JavaClassCS> name2class = new HashMap<@NonNull String, @NonNull JavaClassCS>(); private boolean doneFullScan = false; /* @deprecated use Iterable argument */ @Deprecated public JavaClassScope(@NonNull ClassLoader classLoader) { this.classLoaders.add(classLoader); this.project = null; } public JavaClassScope(@NonNull Iterable<@NonNull ClassLoader> classLoaders) { this.project = null; addClassLoaders(classLoaders); } public JavaClassScope(@NonNull IProject project) { this.project = project; } @Override public void addClassLoaders(@NonNull Iterable<@NonNull ClassLoader> classLoaders) { for (@NonNull ClassLoader classLoader : classLoaders) { if (!this.classLoaders.contains(classLoader)) { this.classLoaders.add(classLoader); } } } private void doFullScan() { Set<@NonNull String> classNames = new HashSet<@NonNull String>(65536); for (@NonNull ClassLoader classLoader : classLoaders) { if (classLoader instanceof BundleReference) { Bundle bundle = ((BundleReference)classLoader).getBundle(); IProject iProject = ResourcesPlugin.getWorkspace().getRoot().getProject(bundle.getSymbolicName()); IJavaProject javaProject = JavaCore.create(iProject); try { IPackageFragmentRoot[] packageFragmentRoots = javaProject.getAllPackageFragmentRoots(); scanJavaElements(packageFragmentRoots, classNames); } catch (JavaModelException e) { } } } if (project != null) { IJavaProject javaProject = JavaCore.create(project); try { IPackageFragmentRoot[] packageFragmentRoots = javaProject.getAllPackageFragmentRoots(); scanJavaElements(packageFragmentRoots, classNames); } catch (JavaModelException e) { } } // else { // scanClassPath(classNames); // scanBundles(classNames); // } for (@NonNull String className : classNames) { getEObjectDescription(className); } } @Override public void getAdapter(@NonNull BaseCSResource importedResource) { if (classLoaders.size() > 0) { getAdapter(importedResource, classLoaders); } else if (project != null) { getAdapter(importedResource, project); } } @Override protected Iterable<IEObjectDescription> getAllLocalElements() { List<IEObjectDescription> results = new ArrayList<IEObjectDescription>(); if (SUPPRESS_WORK_THREAD && !doneFullScan) { doneFullScan = true; doFullScan(); } if (!doneFullScan) { doneFullScan = true; Thread thread = new Thread("OCLstdlib ClassPath Scan") { @Override public void run() { doFullScan(); } }; thread.start(); String name = "Try again once worker thread class path scan has completed."; JavaClassCS csJavaClass = OCLstdlibCSFactory.eINSTANCE.createJavaClassCS(); csJavaClass.setName(name); results.add(EObjectDescription.create(name, csJavaClass)); } else { List<@NonNull String> sortedNames = new ArrayList<@NonNull String>(name2class.keySet()); Collections.sort(sortedNames); for (@NonNull String className : sortedNames) { results.add(getEObjectDescription(className)); } } return results; } protected IEObjectDescription getEObjectDescription(@NonNull String name) { JavaClassCS csJavaClass; synchronized (name2class) { csJavaClass = name2class.get(name); if (csJavaClass == null) { csJavaClass = OCLstdlibCSFactory.eINSTANCE.createJavaClassCS(); csJavaClass.setName(name); name2class.put(name, csJavaClass); } } return EObjectDescription.create(name, csJavaClass); } @Override public Iterable<IEObjectDescription> getElements(QualifiedName name) { IEObjectDescription result = getSingleElement(name); if (result != null) return singleton(result); return emptySet(); } @Override protected Iterable<IEObjectDescription> getLocalElementsByEObject(EObject object, URI uri) { QualifiedName qualifiedName = QualifiedName.create(((JavaClassCS)object).getName()); return Collections.singletonList(EObjectDescription.create(qualifiedName, object)); } @Override protected Iterable<IEObjectDescription> getLocalElementsByName(final QualifiedName name) { Iterable<IEObjectDescription> localElements = getAllLocalElements(); Iterable<IEObjectDescription> result = Iterables.filter(localElements, new Predicate<IEObjectDescription>() { @Override public boolean apply(IEObjectDescription input) { if (isIgnoreCase()) { QualifiedName lowerCase = name.toLowerCase(); QualifiedName inputLowerCase = input.getName().toLowerCase(); return lowerCase.equals(inputLowerCase); } else { return name.equals(input.getName()); } } }); return result; } @Override public IEObjectDescription getSingleElement(QualifiedName qualifiedName) { String name = qualifiedName.toString(); if (name == null) { return null; } JavaClassCS csJavaClass = name2class.get(name); if (csJavaClass == null) { Class<?> loadClass = null; IType type = null; for (@NonNull ClassLoader classLoader : classLoaders) { try { loadClass = classLoader.loadClass(name); if (loadClass != null) { break; } } catch (ClassNotFoundException e) {} } IProject project2 = project; if (project2 != null) { IJavaProject javaProject = JavaCore.create(project2); try { type = javaProject.findType(name); } catch (JavaModelException e) { return null; } } if ((loadClass == null) && (type == null)) { return null; } } return getEObjectDescription(name); } /** * Refresh the known classes in the CS Resource root. */ @Override public void installContents(@NonNull BaseCSResource csResource) { Set<JavaClassCS> javaClasses = new HashSet<JavaClassCS>(); EList<EObject> contents = csResource.getContents(); for (int i = contents.size(); --i >= 0; ) { EObject eObject = contents.get(i); if (eObject instanceof JavaClassCS) { contents.remove(i); } } for (TreeIterator<EObject> tit = csResource.getAllContents(); tit.hasNext(); ) { EObject eObject = tit.next(); if (eObject instanceof JavaImplementationCS) { JavaClassCS implementation = ((JavaImplementationCS)eObject).getImplementation(); if (implementation != null) { javaClasses.add(implementation); } } } contents.addAll(javaClasses); } protected @Nullable String resolveClassName(@NonNull String name) { if (!name.endsWith(".class")) { return null; } String className = name.substring(0, name.length()-6); int dollarIndex = className.lastIndexOf('$'); if ((dollarIndex <= 0) || (className.length() <= dollarIndex+1) || !Character.isDigit(className.charAt(dollarIndex+1))) { return className.replace("/", "."); } else { return null; } } /* private void scanJar(@NonNull File file, @NonNull Set<String> classNames) { // System.out.println("registerBundle " + file); JarFile jarFile = null; try { jarFile = new JarFile(file); for (Enumeration<JarEntry> jarEntries = jarFile.entries(); jarEntries.hasMoreElements(); ) { JarEntry jarEntry = jarEntries.nextElement(); if (!jarEntry.isDirectory()) { String name = jarEntry.getName(); if (name != null) { // System.out.println(" entry " + name); String className = resolveClassName(name); if (className != null) { classNames.add(className); } } } } } catch (Exception e) { } finally{ if (jarFile != null) { try { jarFile.close(); } catch (IOException e) {} } } } */ /* protected @Nullable IProjectDescriptor registerProject(@NonNull File file) { System.out.println("registerProject " + file); FileInputStream inputStream = null; try { inputStream = new FileInputStream(file); Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); String project = document.getDocumentElement().getElementsByTagName("name").item(0).getTextContent(); if (project != null) { @SuppressWarnings("null")@NonNull URI locationURI = URI.createFileURI(file.getParentFile().getCanonicalPath() + File.separator); // IProjectDescriptor projectDescriptor = createProjectDescriptor(project, locationURI); // project2descriptor.put(project, projectDescriptor); return null; } } catch (Exception e) { logException("Couldn't read '" + file + "'", e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } return null; } */ /* private void scanBundles(@NonNull Set<String> classNames) { for (IBundleGroupProvider bundleGroupProvider : Platform.getBundleGroupProviders()) { for (IBundleGroup bundleGroup : bundleGroupProvider.getBundleGroups()) { for (Bundle bundle : bundleGroup.getBundles()) { try { String bundleName = bundle.getSymbolicName(); if (bundleName != null) { String location = bundle.getLocation(); // System.out.println(bundleName + " => " + location); if (location.startsWith("reference:")) { location = location.substring(10); } else { logger.warn("Unknown bundle location " + location); } java.net.URI locationURI = new java.net.URI(location); File file = URIUtil.toFile(locationURI); if (file != null) { if (location.endsWith(".jar")) { scanJar(file, classNames); } else { scanFolder(file, classNames, "", bundleName); } } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } */ /* private void scanClassPath(@NonNull IClasspathEntry @NonNull [] resolvedClasspath, @NonNull Set<String> classNames) { // String property = System.getProperty("java.class.path"); // String separator = System.getProperty("path.separator"); // if (property != null) { // String[] entries = property.split(separator); // for (String entry : entries) { for (IClasspathEntry classpathEntry : resolvedClasspath) { int entryKind = classpathEntry.getEntryKind(); if (entryKind == IClasspathEntry.CPE_SOURCE) { IPath path = classpathEntry.getPath(); File fileEntry = path.toFile(); try { File f = fileEntry.getCanonicalFile(); if (f.getPath().endsWith(".jar")) { scanJar(f, classNames); } else { scanFolder(f, classNames, "", path.toString()); // } // eclipse bin folder? /* File parentFile = f.getParentFile(); File dotProject = new File(parentFile, ".project"); if (dotProject.exists()) { IProjectDescriptor projectDescriptor = registerProject(dotProject); if (projectDescriptor != null) { File plugIn = new File(parentFile, "plugin.xml"); if (plugIn.exists()) { PluginReader pluginReader = new PluginReader(projectDescriptor); saxParser.parse(plugIn, pluginReader); pluginReader.scanContents(saxParser); } } } * / } } catch (Exception e) {} } } } */ /* protected boolean scanFolder(@NonNull File f, @NonNull Set<String> alreadyVisited, int depth) { try { if (!alreadyVisited.add(f.getCanonicalPath())) return true; } catch (Exception e) { logException("Failed to scan '" + f + "'", e); return true; } File[] files = f.listFiles(); boolean containsProject = false; File dotProject = null; if (files != null) { for (File file : files) { if (file.exists() && file.isDirectory() && (depth < 2) && !file.getName().startsWith(".")) { containsProject |= scanFolder(file, alreadyVisited, depth + 1); } else if (".project".equals(file.getName())) { dotProject = file; } else if (file.getName().endsWith(".jar")) { scanJar(file); } } } if (!containsProject && dotProject != null) registerProject(dotProject); return containsProject || dotProject != null; } */ /* private void scanFolder(@NonNull File folder, @NonNull Set<String> classNames, @NonNull String prefix, @NonNull String bundle) { // System.out.println("scanFolder " + folder); File[] files = folder.listFiles(); if (files != null) { for (File file : files) { if (file.exists()) { String name = file.getName(); if (name != null) { int prefixLength = prefix.length(); if (file.isDirectory()) { if ((prefixLength < 10000) && !file.getName().startsWith(".")) { if (prefixLength > 0) { scanFolder(file, classNames, prefix + "." + name, bundle); } else if (bundle.startsWith(name)) { scanFolder(file, classNames, name, bundle); } else { // Skip over output path scanFolder(file, classNames, prefix, bundle); } } } else { // System.out.println(" entry " + name); String className = resolveClassName(name); if (className != null) { System.out.println(" entry " + prefix + "." + className); classNames.add(prefix + "." + className); } } } } } } } */ private void scanJavaElements(IJavaElement[] elements, Set<String> classNames) { for (IJavaElement element : elements) { // System.out.println(getClass().getSimpleName() + " : " + element); if (element instanceof IType) { IType iType = (IType)element; classNames.add(iType.getFullyQualifiedName()); try { if (iType.hasChildren()) { scanJavaElements(iType.getChildren(), classNames); } } catch (JavaModelException e) {} } else if ((element instanceof IParent) && !(element instanceof IMember)) { try { IParent iParent = (IParent)element; if (iParent.hasChildren()) { scanJavaElements(iParent.getChildren(), classNames); } } catch (JavaModelException e) {} } } } }