package org.eclipse.osgi.container;
import java.util.*;
import org.eclipse.osgi.internal.framework.FilterImpl;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.report.resolution.ResolutionReport;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.namespace.*;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.resource.*;
import org.osgi.service.resolver.ResolutionException;
class ModuleResolutionReport implements ResolutionReport {
static class Builder {
private final Map<Resource, List<Entry>> resourceToEntries = new HashMap<>();
public void addEntry(Resource resource, Entry.Type type, Object data) {
List<Entry> entries = resourceToEntries.get(resource);
if (entries == null) {
entries = new ArrayList<>();
resourceToEntries.put(resource, entries);
}
entries.add(new EntryImpl(type, data));
}
public ModuleResolutionReport build(Map<Resource, List<Wire>> resolutionResult, ResolutionException cause) {
return new ModuleResolutionReport(resolutionResult, resourceToEntries, cause);
}
}
static class EntryImpl implements Entry {
private final Object data;
private final Type type;
EntryImpl(Type type, Object data) {
this.type = type;
this.data = data;
}
@Override
public Object getData() {
return data;
}
@Override
public Type getType() {
return type;
}
}
private final Map<Resource, List<Entry>> entries;
private final ResolutionException resolutionException;
private final Map<Resource, List<Wire>> resolutionResult;
ModuleResolutionReport(Map<Resource, List<Wire>> resolutionResult, Map<Resource, List<Entry>> entries, ResolutionException cause) {
this.entries = entries == null ? Collections.<Resource, List<Entry>> emptyMap() : Collections.unmodifiableMap(new HashMap<>(entries));
this.resolutionResult = resolutionResult == null ? Collections.<Resource, List<Wire>> emptyMap() : Collections.unmodifiableMap(resolutionResult);
this.resolutionException = cause;
}
@Override
public Map<Resource, List<Entry>> getEntries() {
return entries;
}
@Override
public ResolutionException getResolutionException() {
return resolutionException;
}
Map<Resource, List<Wire>> getResolutionResult() {
return resolutionResult;
}
private static String getResolutionReport0(String prepend, ModuleRevision revision, Map<Resource, List<ResolutionReport.Entry>> reportEntries, Set<BundleRevision> visited) {
if (prepend == null) {
prepend = "";
}
if (visited == null) {
visited = new HashSet<>();
}
if (visited.contains(revision)) {
return "";
}
visited.add(revision);
StringBuilder result = new StringBuilder();
String id = revision.getRevisions().getModule().getId().toString();
result.append(prepend).append(revision.getSymbolicName()).append(" [").append(id).append("]").append('\n');
List<ResolutionReport.Entry> revisionEntries = reportEntries.get(revision);
if (revisionEntries == null) {
result.append(prepend).append(" ").append(Msg.ModuleResolutionReport_NoReport);
} else {
for (ResolutionReport.Entry entry : revisionEntries) {
printResolutionEntry(result, prepend + " ", entry, reportEntries, visited);
}
}
return result.toString();
}
private static void printResolutionEntry(StringBuilder result, String prepend, ResolutionReport.Entry entry, Map<Resource, List<ResolutionReport.Entry>> reportEntries, Set<BundleRevision> visited) {
switch (entry.getType()) {
case MISSING_CAPABILITY :
result.append(prepend).append(Msg.ModuleResolutionReport_UnresolvedReq).append(printRequirement(entry.getData())).append('\n');
break;
case SINGLETON_SELECTION :
result.append(prepend).append(Msg.ModuleResolutionReport_AnotherSingleton).append(entry.getData()).append('\n');
break;
case UNRESOLVED_PROVIDER :
@SuppressWarnings("unchecked")
Map<Requirement, Set<Capability>> unresolvedProviders = (Map<Requirement, Set<Capability>>) entry.getData();
for (Map.Entry<Requirement, Set<Capability>> unresolvedRequirement : unresolvedProviders.entrySet()) {
Set<Capability> unresolvedCapabilities = unresolvedRequirement.getValue();
if (!unresolvedCapabilities.isEmpty()) {
Capability unresolvedCapability = unresolvedCapabilities.iterator().next();
if (!unresolvedRequirement.getKey().getResource().equals(unresolvedCapability.getResource())) {
result.append(prepend).append(Msg.ModuleResolutionReport_UnresolvedReq).append(printRequirement(unresolvedRequirement.getKey())).append('\n');
result.append(prepend).append(" -> ").append(printCapability(unresolvedCapability)).append('\n');
result.append(getResolutionReport0(prepend + " ", (ModuleRevision) unresolvedCapability.getResource(), reportEntries, visited));
}
}
}
break;
case FILTERED_BY_RESOLVER_HOOK :
result.append(Msg.ModuleResolutionReport_FilteredByHook).append('\n');
break;
case USES_CONSTRAINT_VIOLATION :
result.append(prepend).append(Msg.ModuleResolutionReport_UsesConstraintError).append('\n');
result.append(" ").append(entry.getData());
break;
default :
result.append(Msg.ModuleResolutionReport_Unknown).append("type=").append(entry.getType()).append(" data=").append(entry.getData()).append('\n');
break;
}
}
private static Object printCapability(Capability cap) {
if (PackageNamespace.PACKAGE_NAMESPACE.equals(cap.getNamespace())) {
return Constants.EXPORT_PACKAGE + ": " + createOSGiCapability(cap);
} else if (BundleNamespace.BUNDLE_NAMESPACE.equals(cap.getNamespace())) {
return Constants.BUNDLE_SYMBOLICNAME + ": " + createOSGiCapability(cap);
} else if (HostNamespace.HOST_NAMESPACE.equals(cap.getNamespace())) {
return Constants.BUNDLE_SYMBOLICNAME + ": " + createOSGiCapability(cap);
}
return Constants.PROVIDE_CAPABILITY + ": " + cap.toString();
}
private static String createOSGiCapability(Capability cap) {
Map<String, Object> attributes = new HashMap<>(cap.getAttributes());
Map<String, String> directives = cap.getDirectives();
String name = String.valueOf(attributes.remove(cap.getNamespace()));
return name + ModuleRevision.toString(attributes, false, true) + ModuleRevision.toString(directives, true, true);
}
private static String printRequirement(Object data) {
if (!(data instanceof Requirement)) {
return String.valueOf(data);
}
Requirement req = (Requirement) data;
if (PackageNamespace.PACKAGE_NAMESPACE.equals(req.getNamespace())) {
return Constants.IMPORT_PACKAGE + ": " + createOSGiRequirement(req, PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
} else if (BundleNamespace.BUNDLE_NAMESPACE.equals(req.getNamespace())) {
return Constants.REQUIRE_BUNDLE + ": " + createOSGiRequirement(req, BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
} else if (HostNamespace.HOST_NAMESPACE.equals(req.getNamespace())) {
return Constants.FRAGMENT_HOST + ": " + createOSGiRequirement(req, HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
}
return Constants.REQUIRE_CAPABILITY + ": " + req.toString();
}
private static String createOSGiRequirement(Requirement requirement, String... versions) {
Map<String, String> directives = new HashMap<>(requirement.getDirectives());
String filter = directives.remove(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
if (filter == null)
throw new IllegalArgumentException("No filter directive found:" + requirement);
FilterImpl filterImpl;
try {
filterImpl = FilterImpl.newInstance(filter);
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException("Invalid filter directive", e);
}
Map<String, String> matchingAttributes = filterImpl.getStandardOSGiAttributes(versions);
String name = matchingAttributes.remove(requirement.getNamespace());
if (name == null)
throw new IllegalArgumentException("Invalid requirement: " + requirement);
return name + ModuleRevision.toString(matchingAttributes, false, true) + ModuleRevision.toString(directives, true, true);
}
@Override
public String getResolutionReportMessage(Resource resource) {
return getResolutionReport0(null, (ModuleRevision) resource, getEntries(), null);
}
}