View Javadoc
1   /*-
2    * #%L
3    * io.earcam.instrumental.archive.osgi
4    * %%
5    * Copyright (C) 2018 earcam
6    * %%
7    * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
8    * 
9    * You <b>must</b> choose to accept, in full - any individual or combination of 
10   * the following licenses:
11   * <ul>
12   * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
13   * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
14   * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
15   * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
16   * </ul>
17   * #L%
18   */
19  package io.earcam.instrumental.archive.osgi;
20  
21  import static io.earcam.instrumental.module.auto.Reader.reader;
22  import static java.util.stream.Collectors.toCollection;
23  
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.function.Predicate;
31  import java.util.jar.Manifest;
32  
33  import org.osgi.framework.BundleActivator;
34  
35  import io.earcam.instrumental.archive.AbstractAsJarBuilder;
36  import io.earcam.instrumental.archive.ArchiveRegistrar;
37  import io.earcam.instrumental.archive.ArchiveResource;
38  import io.earcam.instrumental.archive.ArchiveResourceListener;
39  import io.earcam.instrumental.archive.osgi.auto.ClasspathBundles;
40  import io.earcam.instrumental.module.auto.Reader;
41  import io.earcam.instrumental.module.osgi.BundleInfoBuilder;
42  import io.earcam.instrumental.module.osgi.ClauseParameters;
43  import io.earcam.instrumental.reflect.Types;
44  
45  /**
46   * <p>
47   * AsOsgiBundle class.
48   * </p>
49   *
50   */
51  class DefaultAsOsgiBundle extends AbstractAsJarBuilder<AsOsgiBundle> implements AsOsgiBundle, ArchiveResourceListener {
52  
53  	private final BundleInfoBuilder builder = BundleInfoBuilder.bundle();
54  
55  	class ExportMatcher {
56  		final Predicate<String> matcher;
57  		final ClauseParameters parameters;
58  
59  
60  		ExportMatcher(Predicate<String> matcher, ClauseParameters parameters)
61  		{
62  			this.matcher = matcher;
63  			this.parameters = parameters;
64  		}
65  
66  
67  		public boolean test(ArchiveResource resource)
68  		{
69  			String pkg = resource.pkg();
70  			if(matcher.test(pkg)) {
71  				builder.exportPackages(pkg, parameters);
72  				return true;
73  			}
74  			return false;
75  		}
76  	}
77  
78  	class AutoImporting {
79  		final Map<String, Set<String>> imports = new HashMap<>();
80  		final Reader reader = reader()
81  				.addImportListener(imports::put)
82  				.setImportedTypeReducer(Reader::typeToPackageReducer)
83  				.setImportingTypeReducer(Reader::typeToPackageReducer);
84  
85  
86  		void process(ArchiveResource resource)
87  		{
88  			reader.processClass(resource.bytes());
89  		}
90  
91  
92  		Set<String> imported()
93  		{
94  			Predicate<? super String> ownPackages = imports.keySet()::contains;
95  
96  			return imports.values().stream()
97  					.flatMap(Set::stream)
98  					.filter(ownPackages.negate())
99  					.collect(toCollection(HashSet::new));
100 		}
101 
102 	}
103 
104 	private final List<ExportMatcher> exportMatchers = new ArrayList<>();
105 
106 	private final List<PackageBundleMapper> packageBundleMappers = new ArrayList<>();
107 
108 	private AutoImporting autoImporting;
109 
110 
111 	DefaultAsOsgiBundle()
112 	{}
113 
114 
115 	@Override
116 	protected AsOsgiBundle self()
117 	{
118 		return this;
119 	}
120 
121 
122 	@Override
123 	public void attach(ArchiveRegistrar core)
124 	{
125 		super.attach(core);
126 		core.registerResourceListener(this);
127 	}
128 
129 
130 	@Override
131 	public void added(ArchiveResource resource)
132 	{
133 		super.added(resource);
134 
135 		if(autoImporting != null && resource.isClass()) {
136 			autoImporting.process(resource);
137 		}
138 
139 		for(ExportMatcher matcher : exportMatchers) {
140 			if(resource.isQualifiedClass()) {
141 				matcher.test(resource);
142 				if(matcher.test(resource)) {
143 					break;
144 				}
145 			}
146 		}
147 	}
148 
149 
150 	@Override
151 	public void process(Manifest manifest)
152 	{
153 		super.process(manifest);
154 		resolveAutoImports();
155 		builder.to(manifest);
156 	}
157 
158 
159 	private void resolveAutoImports()
160 	{
161 		if(autoImporting == null) {
162 			return;
163 		}
164 		Set<String> imports = autoImporting.imported();
165 
166 		for(PackageBundleMapper mapper : packageBundleMappers) {
167 			mapper.importsFor(imports.iterator())
168 					.forEach(builder::importPackages);
169 		}
170 
171 		if(validate() && !imports.isEmpty()) {
172 			throw new IllegalStateException("' unresolved imports remain: " + imports);
173 		}
174 	}
175 
176 
177 	@Override
178 	public AsOsgiBundle named(String symbolicName, ClauseParameters parameters)
179 	{
180 		builder.symbolicName(symbolicName, parameters);
181 		return this;
182 	}
183 
184 
185 	@Override
186 	public AsOsgiBundle withActivator(Class<?> activator)
187 	{
188 		if(validate()) {
189 			requireActivator(activator);
190 		}
191 		source.with(activator);
192 		return withActivator(activator.getCanonicalName());
193 	}
194 
195 
196 	@Override
197 	public AsOsgiBundle withActivator(String canonicalName)
198 	{
199 		builder.activator(canonicalName);
200 		return this;
201 	}
202 
203 
204 	private static final void requireActivator(Class<?> activator)
205 	{
206 		if(!Types.implementsAll(activator, BundleActivator.class)) {
207 			throw new IllegalArgumentException(activator + " does not implement " + BundleActivator.class);
208 		}
209 	}
210 
211 
212 	@Override
213 	public AsOsgiBundle exporting(Predicate<String> exportMatcher, ClauseParameters parameters)
214 	{
215 		exportMatchers.add(new ExportMatcher(exportMatcher, parameters));
216 		return this;
217 	}
218 
219 
220 	/**
221 	 * <p>
222 	 * exporting.
223 	 * </p>
224 	 *
225 	 * @param type a {@link java.lang.Class} object.
226 	 * @param parameters a {@link io.earcam.instrumental.module.osgi.ClauseParameters} object.
227 	 * @return a {@link io.earcam.instrumental.archive.osgi.DefaultAsOsgiBundle} object.
228 	 */
229 	@Override
230 	public AsOsgiBundle exporting(Class<?> type, ClauseParameters parameters)
231 	{
232 		source.with(type);
233 		return exporting(type.getPackage(), parameters);
234 	}
235 
236 
237 	/**
238 	 * <p>
239 	 * exporting.
240 	 * </p>
241 	 *
242 	 * @param paquet a {@link java.lang.String} object.
243 	 * @param parameters a {@link io.earcam.instrumental.module.osgi.ClauseParameters} object.
244 	 * @return a {@link io.earcam.instrumental.archive.osgi.DefaultAsOsgiBundle} object.
245 	 */
246 	@Override
247 	public AsOsgiBundle exporting(String paquet, ClauseParameters parameters)
248 	{
249 		builder.exportPackages(paquet, parameters);
250 		return this;
251 	}
252 
253 
254 	/**
255 	 * <p>
256 	 * importing.
257 	 * </p>
258 	 *
259 	 * @param paquet a {@link java.lang.String} object.
260 	 * @param parameters a {@link io.earcam.instrumental.module.osgi.ClauseParameters} object.
261 	 * @return a {@link io.earcam.instrumental.archive.osgi.DefaultAsOsgiBundle} object.
262 	 */
263 	@Override
264 	public AsOsgiBundle importing(String paquet, ClauseParameters parameters)
265 	{
266 		builder.importPackages(paquet, parameters);
267 		return this;
268 	}
269 
270 
271 	/**
272 	 * <p>
273 	 * autoImporting.
274 	 * </p>
275 	 *
276 	 * @return a {@link io.earcam.instrumental.archive.osgi.DefaultAsOsgiBundle} object.
277 	 */
278 	@Override
279 	public AsOsgiBundle autoImporting()
280 	{
281 		return autoImporting(new ClasspathBundles());
282 	}
283 
284 
285 	@Override
286 	public AsOsgiBundle autoImporting(List<PackageBundleMapper> mappers)
287 	{
288 		this.autoImporting = new AutoImporting();
289 		mappers.forEach(packageBundleMappers::add);
290 		return this;
291 	}
292 }