package org.graalvm.component.installer.commands;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.graalvm.component.installer.Archive;
import org.graalvm.component.installer.Feedback;
import org.graalvm.component.installer.SystemUtils;
import org.graalvm.component.installer.UserAbortException;
import org.graalvm.component.installer.model.ComponentInfo;
import org.graalvm.component.installer.model.ComponentRegistry;
import org.graalvm.component.installer.persist.MetadataLoader;
import org.graalvm.component.installer.remote.FileDownloader;
public class LicensePresenter {
private final Feedback feedback;
private final ComponentRegistry localRegistry;
private final Map<String, List<MetadataLoader>> licensesToAccept;
private State state = State.NONE;
private boolean multiLicenses;
private String displayLicenseId;
enum State {
LIST,
LISTINPUT,
SINGLE,
LICENSE,
NONE
}
public LicensePresenter(Feedback feedback, ComponentRegistry localRegistry, Map<String, List<MetadataLoader>> licenseIDs) {
this.feedback = feedback.withBundle(LicensePresenter.class);
this.localRegistry = localRegistry;
this.licensesToAccept = licenseIDs;
}
public Map<String, List<MetadataLoader>> getLicensesToAccept() {
return licensesToAccept;
}
public void filterAcceptedLicenses() {
for (String licId : new ArrayList<>(licensesToAccept.keySet())) {
Collection<MetadataLoader> loaders = licensesToAccept.get(licId);
for (MetadataLoader ldr : new ArrayList<>(loaders)) {
ComponentInfo ci = ldr.getComponentInfo();
Date accepted = ldr.isLicenseAccepted(ci, licId);
if (accepted == null) {
accepted = localRegistry.isLicenseAccepted(ci, licId);
}
if (accepted != null) {
feedback.verboseOutput("INSTALL_LicenseAcceptedAt", ldr.getLicenseType(), accepted, ci.getId(), ci.getName());
loaders.remove(ldr);
}
}
if (loaders.isEmpty()) {
licensesToAccept.remove(licId);
}
}
multiLicenses = licensesToAccept.size() > 1;
}
public State getState() {
return state;
}
public boolean isMultiLicenses() {
return multiLicenses;
}
public String getDisplayLicenseId() {
return displayLicenseId;
}
String formatComponentList(String licId) {
List<MetadataLoader> loaders = licensesToAccept.get(licId);
String list = null;
for (MetadataLoader l : loaders) {
ComponentInfo ci = l.getComponentInfo();
if (list == null) {
list = feedback.l10n("INSTALL_LicenseComponentStart", ci.getName());
} else {
list = feedback.l10n("INSTALL_LicenseComponentCont", list, ci.getName());
}
}
return list;
}
void displayLicenseList() {
feedback.output("INSTALL_LicensesToAccept");
int idx = 1;
for (String licId : licensesToAccept.keySet()) {
List<MetadataLoader> loaders = licensesToAccept.get(licId);
String list = formatComponentList(licId);
feedback.output("INSTALL_AcceptLicenseComponents", loaders.get(0).getLicenseType(), list, idx);
idx++;
}
feedback.outputPart("INSTALL_AcceptAllLicensesPrompt");
state = State.LISTINPUT;
}
private static final Pattern ALL_NUMBERS = Pattern.compile("[0-9]+");
boolean isFinished() {
return licensesToAccept.isEmpty();
}
void acceptAllLicenses() {
for (String s : new ArrayList<>(licensesToAccept.keySet())) {
acceptLicense(s);
}
licensesToAccept.clear();
}
boolean isYes(String userInput) {
if (userInput == Feedback.AUTO_YES) {
return true;
}
Pattern p = Pattern.compile(feedback.l10n("INSTALL_AcceptPromptResponseYes@"), Pattern.CASE_INSENSITIVE);
return p.matcher(userInput).matches();
}
boolean isRead(String userInput) {
Pattern p = Pattern.compile(feedback.l10n("INSTALL_AcceptPromptResponseRead@"), Pattern.CASE_INSENSITIVE);
return p.matcher(userInput).matches();
}
int processUserInputForList() {
String userInput = feedback.acceptLine(true);
Pattern p = Pattern.compile(feedback.l10n("INSTALL_AcceptPromptResponseAbort@"), Pattern.CASE_INSENSITIVE);
if (p.matcher(userInput).matches()) {
throw new UserAbortException();
}
if (isYes(userInput)) {
acceptAllLicenses();
state = State.NONE;
return 0;
}
if (!ALL_NUMBERS.matcher(userInput).matches()) {
feedback.output("INSTALL_LicenseNumberInvalidEntry", licensesToAccept.size());
return -1;
}
int n = Integer.parseInt(userInput);
if (n < 0 || n > licensesToAccept.size()) {
feedback.output("INSTALL_LicenseNumberOutOfRange", licensesToAccept.size());
return -1;
}
return n;
}
protected void acceptLicense(String licenseId) {
String licText;
try {
licText = loadLicenseText(licenseId);
} catch (IOException ex) {
throw feedback.failure("INSTALL_ErrorHandlingLicenses", ex, ex.getLocalizedMessage());
}
for (MetadataLoader ldr : licensesToAccept.get(licenseId)) {
Boolean result = null;
try {
result = ldr.recordLicenseAccepted(ldr.getComponentInfo(), licenseId, licText, null);
} catch (IOException ex) {
feedback.error("WARN_LicenseNotRecorded", ex, licenseId, ex.getLocalizedMessage());
}
if (result == null) {
localRegistry.acceptLicense(ldr.getComponentInfo(), licenseId, licText);
}
}
licensesToAccept.remove(licenseId);
}
void displaySingleLicense() {
String licId = licensesToAccept.keySet().iterator().next();
MetadataLoader ldr = licensesToAccept.get(licId).get(0);
String type = ldr.getLicenseType();
String compList = formatComponentList(licId);
feedback.output("INSTALL_AcceptLicense", compList, type);
feedback.outputPart("INSTALL_AcceptSingleLicense");
String input = feedback.acceptLine(true);
if (isYes(input)) {
acceptLicense(licId);
return;
} else if (isRead(input)) {
displayLicenseId = licId;
state = State.LICENSE;
} else {
throw new UserAbortException();
}
}
void displayLicenseText() throws IOException {
String text = loadLicenseText(displayLicenseId);
feedback.verbatimOut(text, false);
feedback.output("INSTALL_AcceptLicensePrompt");
String input = feedback.acceptLine(true);
if (isYes(input)) {
acceptLicense(displayLicenseId);
state = multiLicenses ? State.LIST : State.SINGLE;
} else if (!multiLicenses) {
throw new UserAbortException();
} else {
state = State.LIST;
}
}
boolean isLicenseRemote(String licenseId) {
MetadataLoader ldr = licensesToAccept.get(licenseId).get(0);
String licPath = ldr.getLicensePath();
return SystemUtils.isRemotePath(licPath);
}
String loadLicenseText(String licenseId) throws IOException {
MetadataLoader ldr = licensesToAccept.get(licenseId).get(0);
if (isLicenseRemote(licenseId)) {
return downloadLicenseText(licenseId, ldr);
} else {
return loadFileLicenseText(ldr);
}
}
private Map<String, String> remoteLicenseContents = new HashMap<>();
String downloadLicenseText(String id, MetadataLoader ldr) throws IOException {
String c = remoteLicenseContents.get(id);
if (c != null) {
return c;
}
String t = ldr.getLicenseType();
String label;
if (!t.equals(ldr.getLicensePath())) {
label = feedback.l10n("INSTALL_DownloadLicenseName", t);
} else {
label = feedback.l10n("INSTALL_DownloadLicenseFile");
}
FileDownloader dn = new FileDownloader(
label,
new URL(ldr.getLicensePath()),
feedback);
dn.download();
c = String.join("\n", Files.readAllLines(dn.getLocalFile().toPath()));
remoteLicenseContents.put(id, c);
return c;
}
String loadFileLicenseText(MetadataLoader ldr) throws IOException {
try (Archive a = ldr.getArchive()) {
String licensePath = ldr.getLicensePath();
Archive.FileEntry licenseEntry = null;
for (Archive.FileEntry e : a) {
String n = e.getName();
if (n.startsWith("/")) {
n = n.substring(1);
} else if (n.startsWith("./")) {
n = n.substring(2);
}
if (n.equals(licensePath)) {
licenseEntry = e;
break;
}
}
if (licenseEntry == null) {
throw new IOException(feedback.l10n("INSTALL_LicenseNotFound", licensePath));
}
try (InputStream es = a.getInputStream(licenseEntry);
InputStreamReader esr = new InputStreamReader(es, "UTF-8");
BufferedReader buf = new BufferedReader(esr)) {
return buf.lines().collect(Collectors.joining("\n"));
}
}
}
void init() {
filterAcceptedLicenses();
state = multiLicenses ? State.LIST : State.SINGLE;
}
void run() {
init();
try {
while (!isFinished()) {
singleStep();
}
} catch (IOException ex) {
throw feedback.failure("INSTALL_ErrorHandlingLicenses", ex, ex.getLocalizedMessage());
}
}
void singleStep() throws IOException {
switch (state) {
case LISTINPUT:
int choice = processUserInputForList();
switch (choice) {
case -1:
break;
case 0:
state = State.NONE;
break;
default:
List<String> ids = new ArrayList<>(licensesToAccept.keySet());
displayLicenseId = ids.get(choice - 1);
state = State.LICENSE;
}
break;
case SINGLE:
displaySingleLicense();
break;
case LICENSE:
displayLicenseText();
break;
case NONE:
break;
case LIST:
displayLicenseList();
break;
default:
throw new AssertionError(state.name());
}
}
}