blob: 54679291f618d27baa23fe20296687ca0ed237e2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.gradle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.gradle.util.GradleVersion;
import org.gradle.wrapper.IDownload;
import org.gradle.wrapper.Install;
import org.gradle.wrapper.Logger;
import org.gradle.wrapper.PathAssembler;
import org.gradle.wrapper.WrapperConfiguration;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.modules.gradle.api.NbGradleProject;
import org.netbeans.modules.gradle.spi.GradleSettings;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.awt.Notification;
import org.openide.awt.NotificationDisplayer;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle.Messages;
import org.openide.util.RequestProcessor;
/**
*
* @author Laszlo Kishalmi
*/
public class GradleDistributionManager {
private static final Pattern WRAPPER_DIR_PATTERN = Pattern.compile("gradle-(\\d+\\.\\d+.*)-(bin|all)"); //NOI18N
private static final Pattern DIST_VERSION_PATTERN = Pattern.compile(".*gradle-(\\d+\\.\\d+.*)-(bin|all)\\.zip"); //NOI18N
private static final String DOWNLOAD_URI = "https://services.gradle.org/distributions/gradle-%s-bin.zip"; //NOI18N
private static final RequestProcessor RP = new RequestProcessor("Gradle Installer", 1); //NOI18N
private static final Set<String> VERSION_BLACKLIST = new HashSet<>(Arrays.asList("2.3", "2.13")); //NOI18N
private static final GradleVersion MINIMUM_SUPPORTED_VERSION = GradleVersion.version("2.0"); //NOI18N
private static final Map<File, GradleDistributionManager> CACHE = new WeakHashMap<>();
private static final int JAVA_VERSION;
static {
int ver = 8;
String version = System.getProperty("java.specification.version", System.getProperty("java.version")); //NOI18N
try {
int dot = version.indexOf('.');
ver = dot > 0 ? Integer.parseInt(version.substring(0,dot)) : Integer.parseInt(version);
if (ver == 1) {
version = version.substring(dot + 1);
dot = version.indexOf('.');
ver = dot > 0 ? Integer.parseInt(version.substring(0,dot)) : Integer.parseInt(version);
}
} catch (NumberFormatException ex) {
Exceptions.printStackTrace(ex);
}
JAVA_VERSION = ver;
}
final File gradleUserHome;
private final Map<URI, NbGradleVersion> versions = new HashMap<>();
private GradleDistributionManager(File gradleUserHome) {
this.gradleUserHome = gradleUserHome;
}
public static GradleDistributionManager get(File gradleUserHome) {
GradleDistributionManager ret = CACHE.get(gradleUserHome);
if (ret == null) {
ret = new GradleDistributionManager(gradleUserHome);
CACHE.put(gradleUserHome, ret);
}
return ret;
}
public NbGradleVersion defaultToolingVersion() {
return createVersion(GradleVersion.current().getVersion());
}
public File install(NbGradleVersion version) {
File ret = null;
if (version.install()) {
Lock lock = new ReentrantLock();
PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
if (NbGradleVersion.PROP_AVAILABLE.equals(evt.getPropertyName())) {
synchronized (lock) {
lock.notifyAll();
}
}
};
try {
synchronized (lock) {
version.addPropertyChangeListener(pcl);
lock.wait();
}
} catch (InterruptedException ex) {
return ret;
} finally {
version.removePropertyChangeListener(pcl);
}
ret = version.distributionDir();
} else {
ret = version.distributionDir();
}
return ret;
}
/**
* Tries to evaluate the project distribution. If wrapper
* is preferred and no offline execution is required then it's better to
* leave the GradleConnetor use Installation empty, so the requested
* distribution will be downloaded.
*
* @param rootDir the root directory of the root project.
* @return a the version used in the wrapper or null if there is no wrapper
* or the version cannot be determined.
*/
public NbGradleVersion evaluateGradleWrapperDistribution(File rootDir) {
NbGradleVersion ret = null;
File wrapperProps = new File(rootDir, "gradle/wrapper/gradle-wrapper.properties"); //NOI18N
if (wrapperProps.isFile() && wrapperProps.canRead()) {
Properties wrapper = new Properties();
try (FileInputStream is = new FileInputStream(wrapperProps)) {
wrapper.load(is);
} catch (IOException ex) {
}
String distUrlProp = wrapper.getProperty("distributionUrl"); //NOI18N
if (distUrlProp != null) {
try {
URI distURL = new URI(distUrlProp);
ret = createVersion(distURL);
} catch (URISyntaxException ex) {
// Wrong URL, give up
}
}
}
return ret;
}
public List<NbGradleVersion> availableVersions(boolean releaseOnly) {
List<NbGradleVersion> ret = new ArrayList<>();
JSONParser parser = new JSONParser();
try {
URL allVersions = new URL("https://services.gradle.org/versions/all"); //NOI18N
try (InputStreamReader is = new InputStreamReader(allVersions.openStream(), StandardCharsets.UTF_8)) {
JSONArray versions = (JSONArray) parser.parse(is);
for (Object o : versions) {
JSONObject v = (JSONObject) o;
URI downloadURL = new URI((String) v.get("downloadUrl")); //NOI18N
boolean snapshot = (Boolean) v.get("snapshot"); //NOI18N
boolean nightly = (Boolean) v.get("nightly"); //NOI18N
boolean broken = (Boolean) v.get("broken"); //NOI18N
String version = (String) v.get("version"); //NOI18N
String rcFor = (String) v.get("rcFor"); //NOI18N
if (nightly || broken || snapshot) {
continue;
}
if (!rcFor.isEmpty() && releaseOnly) {
continue;
}
if (GradleVersion.version(version).compareTo(MINIMUM_SUPPORTED_VERSION) < 0) {
continue;
}
ret.add(createVersion(version, downloadURL, rcFor.isEmpty()));
}
} catch (ParseException | IOException | URISyntaxException ex) {
//TODO: Shall we do something about this?
}
} catch (MalformedURLException ex) {
//Shall not happen with hardcoded URL
}
return ret;
}
public List<String> installedVersions(File gradleUserHome) {
List<String> ret = new ArrayList<>();
File wrapperDir = new File(gradleUserHome, "wrapper/dists"); //NOI18N
if (wrapperDir.isDirectory()) {
File[] dirs = wrapperDir.listFiles();
for (File dir : dirs) {
if (dir.isDirectory()) {
Matcher m = WRAPPER_DIR_PATTERN.matcher(dir.getName());
if (m.matches()) {
ret.add(m.group(1));
}
}
}
}
return ret;
}
public NbGradleVersion createVersion(URI distributionUrl) {
NbGradleVersion ret = null;
Matcher m = DIST_VERSION_PATTERN.matcher(distributionUrl.getPath());
if (m.matches()) {
String version = m.group(1);
ret = createVersion(version, distributionUrl, !version.contains("-"));
}
return ret;
}
public NbGradleVersion createVersion(String version) {
try {
URI url = new URI(String.format(DOWNLOAD_URI, version));
return createVersion(version, url, !version.contains("-"));
} catch (URISyntaxException ex) {
//Should not really happen.
}
return null;
}
private NbGradleVersion createVersion(String version, URI url, boolean b) {
NbGradleVersion ret = versions.get(url);
if (ret == null) {
ret = new NbGradleVersion(version, url, b);
versions.put(url, ret);
}
return ret;
}
private static List<File> listDirs(File d) {
List<File> ret = new ArrayList<>();
if (d.isDirectory()) {
for (File f : d.listFiles()) {
if (f.isDirectory()) {
ret.add(f);
}
}
}
return ret;
}
public final class NbGradleVersion implements Comparable<NbGradleVersion>{
public static final String PROP_AVAILABLE = "available"; //NOI18N
final GradleVersion version;
final URI downloadLocation;
final boolean release;
private PropertyChangeSupport pcs;
private NbGradleVersion(String version, URI downloadLocation, boolean release) {
this.version = GradleVersion.version(version);
this.downloadLocation = downloadLocation;
this.release = release;
}
public GradleVersion getVersion() {
return version;
}
public URI getDownloadLocation() {
return downloadLocation;
}
public boolean isRelease() {
return release;
}
public boolean isCompatibleWithSystemJava() {
return JAVA_VERSION < 11 ? true : version.compareTo(GradleVersion.version("4.10.2")) >= 0; //NOI18N
}
public boolean isAvailable() {
File distDir = distributionDir();
return (distDir != null) && distDir.isDirectory();
}
public boolean isBlackListed() {
return VERSION_BLACKLIST.contains(version.getVersion());
}
@Messages("TIT_GradleInstall=Install Gradle")
public boolean install() {
if (!isAvailable()) {
if (GradleSettings.getDefault().isSilentInstall()) {
RP.post(new DownloadTask(this), 500);
return true;
} else {
GradleInstallPanel panel = new GradleInstallPanel(version.getVersion());
DialogDescriptor dd = new DialogDescriptor(panel,
Bundle.TIT_GradleInstall(),
true,
DialogDescriptor.OK_CANCEL_OPTION,
DialogDescriptor.OK_OPTION,
null
);
if (DialogDisplayer.getDefault().notify(dd) == DialogDescriptor.OK_OPTION) {
GradleSettings.getDefault().setSilentInstall(panel.isSilentInstall());
RP.post(new DownloadTask(this), 500);
return true;
}
}
}
return false;
}
public File distributionDir() {
File distDir = distributionBaseDir();
if (distDir.isDirectory()) {
List<File> dirs = listDirs(distDir);
assert dirs.size() <= 1 : "Only one directory allowed in distribution dir"; //NOI18N
if (!dirs.isEmpty()) {
return dirs.get(0);
}
}
//Try to guess something not too wild here
return new File(distDir, "gradle-" + version.getVersion());
}
private File distributionBaseDir() {
WrapperConfiguration conf = new WrapperConfiguration();
conf.setDistribution(downloadLocation);
PathAssembler pa = new PathAssembler(gradleUserHome);
PathAssembler.LocalDistribution dist = pa.getDistribution(conf);
return dist.getDistributionDir();
}
public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
if (pcs == null) {
pcs = new PropertyChangeSupport(this);
}
pcs.addPropertyChangeListener(l);
}
public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
if (pcs != null) {
pcs.removePropertyChangeListener(l);
}
}
void fireVersionAvailable() {
if (pcs != null) {
pcs.firePropertyChange(PROP_AVAILABLE, null, null);
}
}
@Override
public int hashCode() {
return version.hashCode();
}
@Override
public boolean equals(Object obj) {
if ((obj != null) && (obj instanceof NbGradleVersion)) {
return version.equals(((NbGradleVersion) obj).version);
} else {
return false;
}
}
@Override
public String toString() {
return version.getVersion();
}
@Override
public int compareTo(NbGradleVersion o) {
return version.compareTo(o.getVersion());
}
}
private class DownloadTask implements Runnable, IDownload {
private final NbGradleVersion version;
private final ProgressHandle handle;
private final Notification notification;
@Messages({
"# {0} - The downloading GradleVersion ",
"TIT_Download_Gradle=Downloading {0}",
"# {0} - The downloading GradleVersion ",
"MSG_Download_Gradle={0} is being downloaded and installed."
})
public DownloadTask(NbGradleVersion version) {
this.version = version;
handle = ProgressHandleFactory.createSystemHandle(Bundle.TIT_Download_Gradle(version.getVersion()));
notification = NotificationDisplayer.getDefault().notify(
Bundle.TIT_Download_Gradle(version.getVersion()),
NbGradleProject.getIcon(),
Bundle.MSG_Download_Gradle(version.getVersion()),
null,
NotificationDisplayer.Priority.NORMAL,
NotificationDisplayer.Category.INFO);
}
@Messages({
"# {0} - The downloading GradleVersion ",
"TIT_Install_Gradle_Failed=Failed installing {0}",
})
@Override
public void run() {
try {
WrapperConfiguration conf = new WrapperConfiguration();
conf.setDistribution(version.getDownloadLocation());
PathAssembler pa = new PathAssembler(gradleUserHome);
Install install = new Install(new Logger(true), this, pa);
install.createDist(conf);
version.fireVersionAvailable();
} catch (Exception ex) {
//Happens if something goes wrong with the download.
//TODO: Is it ok to let id silently die?
NotificationDisplayer.getDefault().notify(
Bundle.TIT_Install_Gradle_Failed(version.getVersion()),
NbGradleProject.getWarningIcon(),
ex.getLocalizedMessage(),
null,
NotificationDisplayer.Priority.HIGH,
NotificationDisplayer.Category.WARNING);
} finally {
handle.finish();
notification.clear();
}
}
@Override
public void download(URI uri, File file) throws Exception {
URL url = uri.toURL();
URLConnection conn = url.openConnection();
byte[] buf = new byte[8192];
try (FileOutputStream os = new FileOutputStream(file)) {
conn.connect();
int size = conn.getContentLength();
if (size > 0) {
handle.start(size);
} else {
handle.start();
}
int allRead = 0;
int read;
InputStream is = url.openStream();
while ((read = is.read(buf)) > 0) {
os.write(buf, 0, read);
allRead += read;
if (size > 0) {
handle.progress(allRead);
}
}
}
}
}
}