blob: 1eb8eab72f857548ff8007462da42a6d1a7fc828 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2018 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.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
// public Iterable<IEObjectDescription> getAllElements() {
// Iterable<IEObjectDescription> allElements = super.getAllElements();
// System.out.println("getAllElements => " + Iterables.size(allElements));
// return allElements;
// }
@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) {}
}
}
}
}