Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 08cc43ce8e05920d04b724b37a7afbcfbff5e91d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*******************************************************************************
 * Copyright (c) 2013, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
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;

/**
 * A resolution report implementation used by the container for resolution operations.
 * @since 3.10
 */
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 = ""; //$NON-NLS-1$
		}
		if (visited == null) {
			visited = new HashSet<>();
		}
		if (visited.contains(revision)) {
			return ""; //$NON-NLS-1$
		}
		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'); //$NON-NLS-1$ //$NON-NLS-2$

		List<ResolutionReport.Entry> revisionEntries = reportEntries.get(revision);
		if (revisionEntries == null) {
			result.append(prepend).append("  ").append(Msg.ModuleResolutionReport_NoReport); //$NON-NLS-1$
		} else {
			for (ResolutionReport.Entry entry : revisionEntries) {
				printResolutionEntry(result, prepend + "  ", entry, reportEntries, visited); //$NON-NLS-1$
			}
		}
		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()) {
					// for now only printing the first possible unresolved candidates
					Set<Capability> unresolvedCapabilities = unresolvedRequirement.getValue();
					if (!unresolvedCapabilities.isEmpty()) {
						Capability unresolvedCapability = unresolvedCapabilities.iterator().next();
						// make sure this is not a case of importing and exporting the same package
						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'); //$NON-NLS-1$
							result.append(getResolutionReport0(prepend + "     ", (ModuleRevision) unresolvedCapability.getResource(), reportEntries, visited)); //$NON-NLS-1$
						}
					}
				}
				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()); //$NON-NLS-1$
				break;
			default :
				result.append(Msg.ModuleResolutionReport_Unknown).append("type=").append(entry.getType()).append(" data=").append(entry.getData()).append('\n'); //$NON-NLS-1$ //$NON-NLS-2$
				break;
		}
	}

	private static Object printCapability(Capability cap) {
		if (PackageNamespace.PACKAGE_NAMESPACE.equals(cap.getNamespace())) {
			return Constants.EXPORT_PACKAGE + ": " + createOSGiCapability(cap); //$NON-NLS-1$
		} else if (BundleNamespace.BUNDLE_NAMESPACE.equals(cap.getNamespace())) {
			return Constants.BUNDLE_SYMBOLICNAME + ": " + createOSGiCapability(cap); //$NON-NLS-1$
		} else if (HostNamespace.HOST_NAMESPACE.equals(cap.getNamespace())) {
			return Constants.BUNDLE_SYMBOLICNAME + ": " + createOSGiCapability(cap); //$NON-NLS-1$
		}
		return Constants.PROVIDE_CAPABILITY + ": " + cap.toString(); //$NON-NLS-1$
	}

	private static String createOSGiCapability(Capability cap) {
		Map<String, Object> attributes = new HashMap<>(cap.getAttributes());
		Map<String, String> directives = cap.getDirectives();
		String name = (String) 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); //$NON-NLS-1$
		} else if (BundleNamespace.BUNDLE_NAMESPACE.equals(req.getNamespace())) {
			return Constants.REQUIRE_BUNDLE + ": " + createOSGiRequirement(req, BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); //$NON-NLS-1$
		} else if (HostNamespace.HOST_NAMESPACE.equals(req.getNamespace())) {
			return Constants.FRAGMENT_HOST + ": " + createOSGiRequirement(req, HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); //$NON-NLS-1$
		}
		return Constants.REQUIRE_CAPABILITY + ": " + req.toString(); //$NON-NLS-1$
	}

	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); //$NON-NLS-1$
		FilterImpl filterImpl;
		try {
			filterImpl = FilterImpl.newInstance(filter);
		} catch (InvalidSyntaxException e) {
			throw new IllegalArgumentException("Invalid filter directive", e); //$NON-NLS-1$
		}
		Map<String, String> matchingAttributes = filterImpl.getStandardOSGiAttributes(versions);
		String name = matchingAttributes.remove(requirement.getNamespace());
		if (name == null)
			throw new IllegalArgumentException("Invalid requirement: " + requirement); //$NON-NLS-1$
		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);
	}
}

Back to the top