blob: 91d9b247fbf3c45db5387b73b74a648ed5f2720e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2021 Christian Pontesegger 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License_Identifier: EPL-2.0
*
* Contributors:
* Christian Pontesegger - initial API and implementation
*******************************************************************************/
package org.eclipse.ease.ui.completions.java.provider;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.ease.Logger;
import org.eclipse.ease.ui.Activator;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
public final class JavaResources {
private static final JavaResources INSTANCE = new JavaResources();
private static final String PLUGIN_ID = "org.eclipse.ease.ui.completions.java";
private static final Pattern FQN_CLASSNAME_PATTERN = Pattern.compile("(\\p{Lower}+(?:\\.\\p{Lower}+)*)\\.(\\p{Upper}.*)");
private static final Collection<String> FILTERED_PACKAGES = Arrays.asList("java.awt", "java.applet", "groovy", "netscape", "kotlin");
public static JavaResources getInstance() {
return INSTANCE;
}
/** Maps packageName -> {classNames}, eg 'java.io' -> {File, FileBuffer, ...}. */
private Map<String, Collection<String>> fPackagesAndClasses = Collections.emptyMap();
private boolean fisLoadingTriggered = false;
private JavaResources() {
}
public Map<String, Collection<String>> getClasses() {
if (fPackagesAndClasses.isEmpty())
loadClasses();
return fPackagesAndClasses;
}
public Collection<String> getPackages() {
return getClasses().keySet();
}
public Collection<String> getClasses(String packageName) {
if (getClasses().containsKey(packageName))
return getClasses().get(packageName);
return Collections.emptySet();
}
private void loadClasses() {
if (!fisLoadingTriggered) {
fisLoadingTriggered = true;
final Job job = new Job("Load Java code completion") {
@Override
protected IStatus run(IProgressMonitor monitor) {
final Map<String, Collection<String>> loadingContent = new TreeMap<>();
readJavaPackagesAndClasses(loadingContent);
readEclipsePackages(loadingContent);
fPackagesAndClasses = loadingContent;
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
}
private void readEclipsePackages(Map<String, Collection<String>> target) {
final BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext();
for (final Bundle bundle : context.getBundles()) {
for (final String packageName : getExportedPackages(bundle)) {
if (!isFiltered(packageName))
registerPackage(target, packageName);
}
}
}
private void readJavaPackagesAndClasses(Map<String, Collection<String>> target) {
try (BufferedReader reader = getJavaClassDefinitions()) {
String entry = reader.readLine();
while (entry != null) {
final Matcher matcher = FQN_CLASSNAME_PATTERN.matcher(entry);
if (matcher.matches())
registerClass(target, matcher.group(1), matcher.group(2));
entry = reader.readLine();
}
} catch (final IOException e) {
Logger.error(PLUGIN_ID, "Cannot read package list for code completion", e);
}
}
private void registerPackage(Map<String, Collection<String>> target, String packageName) {
if (!target.containsKey(packageName)) {
target.put(packageName, new TreeSet<String>());
}
final int delimiterPosition = packageName.lastIndexOf('.');
if (delimiterPosition > 0)
registerPackage(target, packageName.substring(0, delimiterPosition));
}
private void registerClass(Map<String, Collection<String>> target, final String packageName, String className) {
if (!isFiltered(packageName)) {
registerPackage(target, packageName);
target.get(packageName).add(className);
}
}
private boolean isFiltered(String packageName) {
for (final String filter : FILTERED_PACKAGES) {
if (packageName.startsWith(filter))
return true;
}
return false;
}
private BufferedReader getJavaClassDefinitions() throws IOException {
final URL url = new URL("platform:/plugin/" + PLUGIN_ID + "/resources/java" + getJavaMajorVersion() + " classes.txt");
return new BufferedReader(new InputStreamReader(url.openConnection().getInputStream()));
}
/**
* Get the major version number of the java runtime.
*
* @return java major version, eg 6, 8, 12
*/
private int getJavaMajorVersion() {
int result = 1000; // set to a high value so that the min function below will pick the highest supported version
final Pattern versionPattern = Pattern.compile("(?:1\\.)?(\\d+).*");
final String version = System.getProperty("java.runtime.version");
final Matcher matcher = versionPattern.matcher(version);
if (matcher.matches())
result = Integer.parseInt(matcher.group(1));
return Math.min(result, Activator.JAVA_CLASSES_MAX_VERSION);
}
/**
* Get a list of all exported packages of a bundle.
*
* @param bundle
* bundle instance
* @return collection of exported packages
*/
private Collection<String> getExportedPackages(final Bundle bundle) {
final Collection<String> exportedPackages = new HashSet<>();
final String exportPackage = bundle.getHeaders().get("Export-Package");
if (exportPackage != null) {
final String[] packages = exportPackage.split(",");
for (final String packageEntry : packages) {
String candidate = packageEntry.trim().split(";")[0];
if (candidate.endsWith("\""))
candidate = candidate.substring(0, candidate.length() - 1);
if ((candidate.contains(".internal")) || (packageEntry.contains(";x-internal:=true")))
// ignore internal packages
continue;
if ((candidate.startsWith("Lib")) || (candidate.startsWith("about_files")) || (candidate.startsWith("META")))
// ignore some dedicated packages
continue;
exportedPackages.add(candidate);
}
}
return exportedPackages;
}
}