package org.graalvm.component.installer.remote;
import java.io.IOException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.graalvm.component.installer.Feedback;
import org.graalvm.component.installer.Version;
import org.graalvm.component.installer.model.ComponentInfo;
import org.graalvm.component.installer.model.ComponentRegistry;
import org.graalvm.component.installer.model.RemoteInfoProcessor;
import org.graalvm.component.installer.persist.AbstractCatalogStorage;
import org.graalvm.component.installer.persist.ComponentPackageLoader;
public class RemotePropertiesStorage extends AbstractCatalogStorage {
protected static final String PROPERTY_HASH = "hash";
private static final String FORMAT_FLAVOUR = "Component\\.{0}";
private static final String FORMAT_SINGLE_VERSION = "Component\\.{0}_{1}\\.";
private final Properties catalogProperties;
private final String flavourRegex;
private final String singleRegex;
private final Version graalVersion;
private RemoteInfoProcessor remoteProcessor = RemoteInfoProcessor.NONE;
private Map<String, Properties> filteredComponents;
public RemotePropertiesStorage(Feedback fb, ComponentRegistry localReg, Properties catalogProperties, String graalSelector, Version gVersion, URL baseURL) {
super(localReg, fb, baseURL);
this.catalogProperties = catalogProperties;
flavourRegex = MessageFormat.format(FORMAT_FLAVOUR, graalSelector);
graalVersion = gVersion != null ? gVersion : localReg.getGraalVersion();
singleRegex = MessageFormat.format(FORMAT_SINGLE_VERSION,
graalVersion.originalString(), graalSelector);
}
public RemoteInfoProcessor getRemoteProcessor() {
return remoteProcessor;
}
public void setRemoteProcessor(RemoteInfoProcessor remoteProcessor) {
this.remoteProcessor = remoteProcessor;
}
Properties filterPropertiesForVersions(String id) {
splitPropertiesToComponents();
return filteredComponents.get(id);
}
private void splitPropertiesToComponents() {
if (filteredComponents != null) {
return;
}
filteredComponents = new HashMap<>();
Set<String> knownPrefixes = new HashSet<>();
Set<String> acceptedPrefixes = new HashSet<>();
Pattern flavourPattern = Pattern.compile("^" + flavourRegex, Pattern.CASE_INSENSITIVE);
Pattern singlePattern = Pattern.compile("^" + singleRegex, Pattern.CASE_INSENSITIVE);
for (String s : catalogProperties.stringPropertyNames()) {
String cid;
String pn;
int slashPos = s.indexOf('/');
int secondSlashPos = s.indexOf('/', slashPos + 1);
int l;
if (slashPos != -1 && secondSlashPos != -1) {
if (!flavourPattern.matcher(s).find()) {
continue;
}
pn = s.substring(slashPos + 1);
String pref = s.substring(0, secondSlashPos);
int lastSlashPos = s.indexOf('/', secondSlashPos + 1);
if (lastSlashPos == -1) {
lastSlashPos = secondSlashPos;
}
l = lastSlashPos + 1;
if (knownPrefixes.add(pref)) {
try {
Version vn = Version.fromString(s.substring(slashPos + 1, secondSlashPos));
if (acceptsVersion(vn)) {
acceptedPrefixes.add(pref);
}
} catch (IllegalArgumentException ex) {
feedback.verboseOutput("REMOTE_BadComponentVersion", pn);
}
}
if (!acceptedPrefixes.contains(pref)) {
continue;
}
} else {
Matcher m = singlePattern.matcher(s);
if (!m.find()) {
continue;
}
l = m.end();
pn = graalVersion.toString() + "/" + s.substring(l);
}
int dashPos = s.indexOf("-", l);
if (dashPos == -1) {
dashPos = s.length();
}
cid = s.substring(l, dashPos);
String patchedCid = cid.replace("_", "-");
String patchedPn = pn.replace(cid, patchedCid);
filteredComponents.computeIfAbsent(patchedCid, (unused) -> new Properties()).setProperty(patchedPn, catalogProperties.getProperty(s));
}
}
boolean acceptsVersion(Version vers) {
return graalVersion.installVersion().compareTo(vers.installVersion()) <= 0;
}
@Override
public Set<String> listComponentIDs() throws IOException {
Set<String> ret = new HashSet<>();
splitPropertiesToComponents();
ret.addAll(filteredComponents.keySet());
return ret;
}
@Override
public Set<ComponentInfo> loadComponentMetadata(String id) throws IOException {
Properties compProps = filterPropertiesForVersions(id);
if (compProps == null) {
return null;
}
Map<String, ComponentInfo> infos = new HashMap<>();
Set<String> processedPrefixes = new HashSet<>();
for (String s : compProps.stringPropertyNames()) {
int slashPos = s.indexOf('/');
int anotherSlashPos = s.indexOf('/', slashPos + 1);
String vS = s.substring(0, slashPos);
String identity = anotherSlashPos == -1 ? vS : s.substring(0, anotherSlashPos);
if (!processedPrefixes.add(identity)) {
continue;
}
try {
Version.fromString(vS);
} catch (IllegalArgumentException ex) {
feedback.verboseOutput("REMOTE_BadComponentVersion", s);
continue;
}
ComponentInfo ci = createVersionedComponent(identity + "/", compProps, id,
anotherSlashPos == -1 ? "" : s.substring(slashPos + 1, anotherSlashPos));
if (ci != null) {
infos.put(identity, ci);
}
}
return new HashSet<>(infos.values());
}
private ComponentInfo createVersionedComponent(String versoPrefix, Properties filtered, String id, String tag) throws IOException {
URL downloadURL;
String s = filtered.getProperty(versoPrefix + id.toLowerCase());
if (s == null) {
return null;
}
downloadURL = new URL(baseURL, s);
String prefix = versoPrefix + id.toLowerCase() + "-";
String hashS = filtered.getProperty(prefix + PROPERTY_HASH);
byte[] hash = hashS == null ? null : toHashBytes(id, hashS);
ComponentPackageLoader ldr = new ComponentPackageLoader(tag,
new PrefixedPropertyReader(prefix, filtered),
feedback);
ComponentInfo info = ldr.createComponentInfo();
info.setRemoteURL(downloadURL);
info.setShaDigest(hash);
info.setOrigin(baseURL.toString());
return configureComponentInfo(info);
}
protected ComponentInfo configureComponentInfo(ComponentInfo info) {
return remoteProcessor.decorateComponent(info);
}
static class PrefixedPropertyReader implements Function<String, String> {
private final String compPrefix;
private final Properties props;
PrefixedPropertyReader(String compPrefix, Properties props) {
this.compPrefix = compPrefix;
this.props = props;
}
@Override
public String apply(String t) {
return props.getProperty(compPrefix + t);
}
}
}