DefaultAsOsgiBundle.java
/*-
* #%L
* io.earcam.instrumental.archive.osgi
* %%
* Copyright (C) 2018 earcam
* %%
* SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
*
* You <b>must</b> choose to accept, in full - any individual or combination of
* the following licenses:
* <ul>
* <li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
* <li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
* <li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
* <li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
* </ul>
* #L%
*/
package io.earcam.instrumental.archive.osgi;
import static io.earcam.instrumental.module.auto.Reader.reader;
import static java.util.stream.Collectors.toCollection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.jar.Manifest;
import org.osgi.framework.BundleActivator;
import io.earcam.instrumental.archive.AbstractAsJarBuilder;
import io.earcam.instrumental.archive.ArchiveRegistrar;
import io.earcam.instrumental.archive.ArchiveResource;
import io.earcam.instrumental.archive.ArchiveResourceListener;
import io.earcam.instrumental.archive.osgi.auto.ClasspathBundles;
import io.earcam.instrumental.module.auto.Reader;
import io.earcam.instrumental.module.osgi.BundleInfoBuilder;
import io.earcam.instrumental.module.osgi.ClauseParameters;
import io.earcam.instrumental.reflect.Types;
/**
* <p>
* AsOsgiBundle class.
* </p>
*
*/
class DefaultAsOsgiBundle extends AbstractAsJarBuilder<AsOsgiBundle> implements AsOsgiBundle, ArchiveResourceListener {
private final BundleInfoBuilder builder = BundleInfoBuilder.bundle();
class ExportMatcher {
final Predicate<String> matcher;
final ClauseParameters parameters;
ExportMatcher(Predicate<String> matcher, ClauseParameters parameters)
{
this.matcher = matcher;
this.parameters = parameters;
}
public boolean test(ArchiveResource resource)
{
String pkg = resource.pkg();
if(matcher.test(pkg)) {
builder.exportPackages(pkg, parameters);
return true;
}
return false;
}
}
class AutoImporting {
final Map<String, Set<String>> imports = new HashMap<>();
final Reader reader = reader()
.addImportListener(imports::put)
.setImportedTypeReducer(Reader::typeToPackageReducer)
.setImportingTypeReducer(Reader::typeToPackageReducer);
void process(ArchiveResource resource)
{
reader.processClass(resource.bytes());
}
Set<String> imported()
{
Predicate<? super String> ownPackages = imports.keySet()::contains;
return imports.values().stream()
.flatMap(Set::stream)
.filter(ownPackages.negate())
.collect(toCollection(HashSet::new));
}
}
private final List<ExportMatcher> exportMatchers = new ArrayList<>();
private final List<PackageBundleMapper> packageBundleMappers = new ArrayList<>();
private AutoImporting autoImporting;
DefaultAsOsgiBundle()
{}
@Override
protected AsOsgiBundle self()
{
return this;
}
@Override
public void attach(ArchiveRegistrar core)
{
super.attach(core);
core.registerResourceListener(this);
}
@Override
public void added(ArchiveResource resource)
{
super.added(resource);
if(autoImporting != null && resource.isClass()) {
autoImporting.process(resource);
}
for(ExportMatcher matcher : exportMatchers) {
if(resource.isQualifiedClass()) {
matcher.test(resource);
if(matcher.test(resource)) {
break;
}
}
}
}
@Override
public void process(Manifest manifest)
{
super.process(manifest);
resolveAutoImports();
builder.to(manifest);
}
private void resolveAutoImports()
{
if(autoImporting == null) {
return;
}
Set<String> imports = autoImporting.imported();
for(PackageBundleMapper mapper : packageBundleMappers) {
mapper.importsFor(imports.iterator())
.forEach(builder::importPackages);
}
if(validate() && !imports.isEmpty()) {
throw new IllegalStateException("' unresolved imports remain: " + imports);
}
}
@Override
public AsOsgiBundle named(String symbolicName, ClauseParameters parameters)
{
builder.symbolicName(symbolicName, parameters);
return this;
}
@Override
public AsOsgiBundle withActivator(Class<?> activator)
{
if(validate()) {
requireActivator(activator);
}
source.with(activator);
return withActivator(activator.getCanonicalName());
}
@Override
public AsOsgiBundle withActivator(String canonicalName)
{
builder.activator(canonicalName);
return this;
}
private static final void requireActivator(Class<?> activator)
{
if(!Types.implementsAll(activator, BundleActivator.class)) {
throw new IllegalArgumentException(activator + " does not implement " + BundleActivator.class);
}
}
@Override
public AsOsgiBundle exporting(Predicate<String> exportMatcher, ClauseParameters parameters)
{
exportMatchers.add(new ExportMatcher(exportMatcher, parameters));
return this;
}
/**
* <p>
* exporting.
* </p>
*
* @param type a {@link java.lang.Class} object.
* @param parameters a {@link io.earcam.instrumental.module.osgi.ClauseParameters} object.
* @return a {@link io.earcam.instrumental.archive.osgi.DefaultAsOsgiBundle} object.
*/
@Override
public AsOsgiBundle exporting(Class<?> type, ClauseParameters parameters)
{
source.with(type);
return exporting(type.getPackage(), parameters);
}
/**
* <p>
* exporting.
* </p>
*
* @param paquet a {@link java.lang.String} object.
* @param parameters a {@link io.earcam.instrumental.module.osgi.ClauseParameters} object.
* @return a {@link io.earcam.instrumental.archive.osgi.DefaultAsOsgiBundle} object.
*/
@Override
public AsOsgiBundle exporting(String paquet, ClauseParameters parameters)
{
builder.exportPackages(paquet, parameters);
return this;
}
/**
* <p>
* importing.
* </p>
*
* @param paquet a {@link java.lang.String} object.
* @param parameters a {@link io.earcam.instrumental.module.osgi.ClauseParameters} object.
* @return a {@link io.earcam.instrumental.archive.osgi.DefaultAsOsgiBundle} object.
*/
@Override
public AsOsgiBundle importing(String paquet, ClauseParameters parameters)
{
builder.importPackages(paquet, parameters);
return this;
}
/**
* <p>
* autoImporting.
* </p>
*
* @return a {@link io.earcam.instrumental.archive.osgi.DefaultAsOsgiBundle} object.
*/
@Override
public AsOsgiBundle autoImporting()
{
return autoImporting(new ClasspathBundles());
}
@Override
public AsOsgiBundle autoImporting(List<PackageBundleMapper> mappers)
{
this.autoImporting = new AutoImporting();
mappers.forEach(packageBundleMappers::add);
return this;
}
}