Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package aQute.bnd.osgi;
import static aQute.bnd.exceptions.FunctionWithException.asFunctionOrElse;
import static aQute.bnd.exceptions.PredicateWithException.asPredicate;
import static aQute.libg.generics.Create.list;
import static aQute.libg.generics.Create.map;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.osgi.framework.Constants.RESOLUTION_MANDATORY;
import static org.osgi.framework.Constants.RESOLUTION_OPTIONAL;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringJoiner;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.annotation.Export;
import aQute.bnd.apiguardian.api.API;
import aQute.bnd.classindex.ClassIndexerAnalyzer;
import aQute.bnd.exceptions.ConsumerWithException;
import aQute.bnd.exceptions.Exceptions;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
import aQute.bnd.http.HttpClient;
import aQute.bnd.osgi.Clazz.JAVA;
import aQute.bnd.osgi.Clazz.QUERY;
import aQute.bnd.osgi.Descriptors.Descriptor;
import aQute.bnd.osgi.Descriptors.PackageRef;
import aQute.bnd.osgi.Descriptors.TypeRef;
import aQute.bnd.service.AnalyzerPlugin;
import aQute.bnd.service.ManifestPlugin;
import aQute.bnd.service.OrderedPlugin;
import aQute.bnd.service.classparser.ClassParser;
import aQute.bnd.signatures.ClassSignature;
import aQute.bnd.signatures.FieldSignature;
import aQute.bnd.signatures.MethodSignature;
import aQute.bnd.stream.MapStream;
import aQute.bnd.version.Version;
import aQute.bnd.version.VersionRange;
import aQute.lib.base64.Base64;
import aQute.lib.collections.Iterables;
import aQute.lib.collections.MultiMap;
import aQute.lib.collections.SortedList;
import aQute.lib.date.Dates;
import aQute.lib.filter.Filter;
import aQute.lib.hex.Hex;
import aQute.lib.io.IO;
import aQute.lib.strings.Strings;
import aQute.lib.utf8properties.UTF8Properties;
import aQute.libg.cryptography.Digester;
import aQute.libg.cryptography.MD5;
import aQute.libg.cryptography.SHA1;
import aQute.libg.generics.Create;
import aQute.libg.glob.Glob;
import aQute.libg.reporter.ReporterMessages;
import aQute.libg.tuple.Pair;
/**
* This class can calculate the required headers for a (potential) JAR file. It
* analyzes a directory or JAR for the packages that are contained and that are
* referred to by the bytecodes. The user can the use regular expressions to
* define the attributes and directives. The matching is not fully regex for
* convenience. A * and ? get a . prefixed and dots are escaped.
*
*
* *;auto=true any
* org.acme.*;auto=true org.acme.xyz
* org.[abc]*;auto=true org.acme.xyz
*
*
* Additional, the package instruction can start with a '=' or a '!'. The '!'
* indicates negation. Any matching package is removed. The '=' is literal, the
* expression will be copied verbatim and no matching will take place. Any
* headers in the given properties are used in the output properties.
*/
public class Analyzer extends Processor {
private final static Logger logger = LoggerFactory.getLogger(Analyzer.class);
private final static Version frameworkR7 = new Version("1.9");
private final SortedSet ees = new TreeSet<>();
// Bundle parameters
private Jar dot;
private final Packages contained = new Packages();
private final Packages referred = new Packages();
private Packages exports;
private Packages imports;
private TypeRef activator;
// Global parameters
private final MultiMap uses = new MultiMap<>(PackageRef.class,
PackageRef.class, true);
private final MultiMap apiUses = new MultiMap<>(PackageRef.class,
PackageRef.class, true);
private final Contracts contracts = new Contracts(this);
private final Packages classpathExports = new Packages();
private final Descriptors descriptors = new Descriptors();
private final List classpath = list();
private final Map classspace = map();
private final Map lookAsideClasses = map();
private final Map importedClassesCache = map();
private boolean analyzed = false;
private boolean diagnostics = false;
private boolean inited = false;
final protected AnalyzerMessages msgs = ReporterMessages.base(this,
AnalyzerMessages.class);
private AnnotationHeaders annotationHeaders;
private final Set packagesVisited = new HashSet<>();
private Set nonClassReferences = new HashSet<>();
private Set checks;
private final Map bcpTypes = map();
final TypeRef providerType = getTypeRef(
"org/osgi/annotation/versioning/ProviderType");
public enum Check {
ALL,
IMPORTS,
EXPORTS;
}
public Analyzer() {}
public Analyzer(Jar jar) throws Exception {
this.dot = Objects.requireNonNull(jar, "the jar must not be null");
Manifest manifest = dot.getManifest();
if (manifest != null)
copyFrom(Domain.domain(manifest));
}
public Analyzer(Processor parent) {
super(parent);
}
@Override
protected void setTypeSpecificPlugins(PluginsContainer pluginsContainer) {
super.setTypeSpecificPlugins(pluginsContainer);
pluginsContainer.add(new ClassIndexerAnalyzer());
}
/**
* Specifically for Maven
*/
public static Properties getManifest(File dirOrJar) throws Exception {
try (Analyzer analyzer = new Analyzer()) {
analyzer.setJar(dirOrJar);
Properties properties = new UTF8Properties();
properties.put(IMPORT_PACKAGE, "*");
properties.put(EXPORT_PACKAGE, "*");
analyzer.setProperties(properties);
Manifest m = analyzer.calcManifest();
Properties result = new UTF8Properties();
for (Object key : m.getMainAttributes()
.keySet()) {
Attributes.Name name = (Attributes.Name) key;
result.put(name.toString(), m.getMainAttributes()
.getValue(name));
}
return result;
}
}
/**
* Calculates the data structures for generating a manifest.
*
* @throws IOException
*/
public void analyze() throws Exception {
bracketed(this::analyze0);
}
private void analyze0() throws Exception {
if (!analyzed) {
analyzed = true;
analyzeContent();
Instructions instructions = new Instructions(
OSGiHeader.parseHeader(getProperty(Constants.BUNDLEANNOTATIONS, "*")));
annotationHeaders = new AnnotationHeaders(this, instructions);
doPlugins();
//
// calculate class versions in use
//
classspace.values()
.stream()
.filter(c -> !c.isModule())
.map(Clazz::getFormat)
.forEach(ees::add);
try (ClassDataCollectors cds = new ClassDataCollectors(this)) {
List parsers = getPlugins(ClassParser.class);
for (ClassParser cp : parsers) {
cds.add(cp.getClassDataCollector(this));
}
//
// built ins
//
cds.add(annotationHeaders);
for (Clazz c : classspace.values()) {
cds.parse(c);
}
}
referred.keySet()
.removeAll(contained.keySet());
//
// EXPORTS
//
{
Set unused = Create.set();
Instructions filter = new Instructions(getExportPackage());
filter.appendIfAbsent(getExportContents());
//
// get the packages exported by the annotation. However,
// we remove any explicitly explicitly named private package so
// that
// you can always override an annotation
//
Parameters exportedByAnnotation = getExportedByAnnotation();
exportedByAnnotation.keySet()
.removeAll(getPrivatePackage().keySet());
filter.appendIfAbsent(exportedByAnnotation);
exports = filter(filter, contained, unused);
if (!unused.isEmpty()) {
warning("Unused " + Constants.EXPORT_PACKAGE + " instructions: %s ", unused)
.header(Constants.EXPORT_PACKAGE)
.context(unused.iterator()
.next()
.getInput());
}
// See what information we can find to augment the
// exports. I.e. look on the classpath
augmentExports(exports);
}
//
// IMPORTS
// Imports MUST come after exports because we use information from
// the exports
//
{
// Add all exports that do not have an -noimport: directive
// to the imports.
Packages referredAndExported = new Packages(referred);
referredAndExported.putAll(doExportsToImports(exports));
removeDynamicImports(referredAndExported);
getHostPackages().ifPresent(hostPackages -> referredAndExported.keySet()
.removeAll(hostPackages));
getRequireBundlePackages().ifPresent(hostPackages -> referredAndExported.keySet()
.removeAll(hostPackages));
String h = getProperty(IMPORT_PACKAGE);
if (h == null) // If not set use a default
h = "*";
if (isPedantic() && h.trim()
.length() == 0)
warning("Empty " + Constants.IMPORT_PACKAGE + " header");
Instructions filter = new Instructions(h);
Set unused = Create.set();
imports = filter(filter, referredAndExported, unused);
if (!unused.isEmpty()) {
// We ignore the end wildcard catch
if (!(unused.size() == 1 && unused.iterator()
.next()
.isAny()))
warning("Unused " + Constants.IMPORT_PACKAGE + " instructions: %s ", unused)
.header(Constants.IMPORT_PACKAGE)
.context(unused.iterator()
.next()
.getInput());
}
augmentImports(imports, exports);
boolean frameworkSupportsJavaImports = getExportVersion(getPackageRef("org.osgi.framework"))
.map(actual -> actual.compareTo(frameworkR7) >= 0)
.orElse(true);
boolean elideImports = is(NOIMPORTJAVA) || !frameworkSupportsJavaImports;
if (elideImports) {
imports.keySet()
.removeIf(PackageRef::isJava);
}
}
//
// USES
//
// Add the uses clause to the exports
boolean api = true; // brave,
// lets see
doUses(exports, api ? apiUses : uses, imports);
//
// Verify that no exported package has a reference to a private
// package
// This can cause a lot of harm.
// TODO restrict the check to public API only, but even then
// exported packages
// should preferably not refer to private packages.
//
Set privatePackages = getPrivates();
// References to java are not imported so they would show up as
// private
// packages, lets kill them as well.
privatePackages.removeIf(PackageRef::isJava);
for (PackageRef exported : exports.keySet()) {
List used = uses.get(exported);
if (used != null) {
List apiUsed = apiUses.get(exported);
if (apiUsed != null) {
Set privateReferences = new TreeSet<>(apiUsed);
privateReferences.retainAll(privatePackages);
if (!privateReferences.isEmpty())
msgs.Export_Has_PrivateReferences_(exported, privateReferences.size(), privateReferences);
}
}
}
//
// Checks
//
if (referred.containsKey(Descriptors.DEFAULT_PACKAGE)) {
error(
"The default package '.' is not permitted by the " + Constants.IMPORT_PACKAGE + " syntax.%n"
+ " This can be caused by compile errors in Eclipse because Eclipse creates%n"
+ "valid class files regardless of compile errors.%n"
+ "The following package(s) import from the default package %s",
uses.transpose()
.get(Descriptors.DEFAULT_PACKAGE));
}
// Check for use of the deprecated bnd @Export annotation
TypeRef bndAnnotation = descriptors.getTypeRefFromFQN(aQute.bnd.annotation.Export.class.getName());
contained.keySet()
.stream()
.map(this::getPackageInfo)
.filter(Objects::nonNull)
.distinct()
.filter(clz -> clz.annotations()
.contains(bndAnnotation))
.map(Clazz::getClassName)
.map(TypeRef::getPackageRef)
.map(PackageRef::getFQN)
.forEach(fqn -> warning(
"The annotation aQute.bnd.annotation.Export applied to package %s is deprecated and will be removed in a future release. The org.osgi.annotation.bundle.Export should be used instead",
fqn));
}
}
/**
* Get the export version of a package
*
* @param packageRef the package reference
* @return the version or empty
*/
private Optional getExportVersion(PackageRef packageRef) {
Attrs attrs = classpathExports.get(packageRef);
String version;
if (attrs == null || (version = attrs.getVersion()) == null || !Verifier.isVersion(version))
return Optional.empty();
return Optional.of(Version.parseVersion(version));
}
public void reset() {
contained.clear();
classspace.clear();
referred.clear();
uses.clear();
apiUses.clear();
classpathExports.clear();
contracts.clear();
packagesVisited.clear();
nonClassReferences.clear();
bcpTypes.clear();
}
private void analyzeContent() throws Exception {
// Parse all the classes in the
// the jar according to the OSGi Bundle-ClassPath
analyzeBundleClasspath();
//
// Get exported packages from the
// entries on the classpath and
// collect any contracts
//
for (Jar current : getClasspath()) {
getManifestInfoFromClasspath(current, classpathExports, contracts);
Manifest m = current.getManifest();
if (m == null) {
for (String dir : current.getDirectories()
.keySet()) {
learnPackage(current, "", getPackageRef(dir), classpathExports);
}
}
}
addDefinedContracts();
// Handle the bundle activator
String s = getProperty(BUNDLE_ACTIVATOR);
if (s != null && !s.isEmpty()) {
activator = getTypeRefFromFQN(s);
referTo(activator);
logger.debug("activator {} {}", s, activator);
}
// Conditional packages
doConditionalPackages();
}
/**
* Get the packages from the host if this is a fragment bundle
*
* @return the host packages or an empty set if not a fragment
*/
public Optional> getHostPackages() {
Entry host = getFragmentHost();
if (host != null) {
Jar jar = toJar(host);
if (jar != null) {
return Optional.of(jar.getDirectories()
.keySet()
.stream()
.map(this::getPackageRef)
.filter(this::isNormalPackage)
.collect(toSet()));
}
}
return Optional.empty();
}
/**
* Find the packages belonging to the required bundles
*
* @return the packages from the required bundles, with no Require-Bundle
* return an empty Optional
*/
public Optional> getRequireBundlePackages() {
Parameters required = getRequireBundle();
if (required.isEmpty())
return Optional.empty();
Set refs = required.entrySet()
.stream()
.map(this::toJar)
.filter(Objects::nonNull)
.map(asFunctionOrElse(Jar::getManifest, null))
.filter(Objects::nonNull)
.flatMap(manifest -> {
Domain domain = Domain.domain(manifest);
Parameters exportPackages = domain.getExportPackage();
return exportPackages.keySet()
.stream();
})
.map(this::getPackageRef)
.filter(this::isNormalPackage)
.collect(toSet());
return Optional.of(refs);
}
private boolean isNormalPackage(PackageRef pRef) {
return !pRef.isJava() && !pRef.isMetaData();
}
private Jar toJar(Map.Entry host) {
String bsn = host.getKey();
String v = host.getValue()
.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
Jar jar = findClasspathEntry(bsn, v);
if ((jar == null) && !"system.bundle".equals(bsn)) {
warning("Host %s for this fragment/require bundle cannot be found on the classpath", host);
}
return jar;
}
private final static String STATUS_PROPERTY = "status";
private Parameters getExportedByAnnotation() {
TypeRef exportAnnotation = descriptors.getTypeRef("org/osgi/annotation/bundle/Export");
Parameters exportedByAnnotation = getContained().keySet()
.stream()
.map(this::getPackageInfo)
.filter(Objects::nonNull)
.distinct()
.filter(c -> c.annotations()
.contains(exportAnnotation))
.map(Clazz::getClassName)
.map(TypeRef::getPackageRef)
.map(PackageRef::getFQN)
.collect(Parameters.toParameters());
// Opt-in is required for processing API Guardian annotations
Parameters headerAPIGuardian = OSGiHeader.parseHeader(getProperty(Constants.EXPORT_APIGUARDIAN));
if (headerAPIGuardian.isEmpty()) {
return exportedByAnnotation;
}
Instructions instructions = new Instructions(headerAPIGuardian);
TypeRef apiAnnotation = descriptors.getTypeRef("org/apiguardian/api/API");
Parameters exportedByAPIGuardian = new Parameters(false);
MapStream.of(getClassspace().values()
.stream()
.filter(c -> !c.isModule() && !c.isInnerClass() && (!c.isSynthetic() || c.isPackageInfo())
&& c.annotations()
.contains(apiAnnotation))
.flatMap(c -> instructions.matchesStream(c.getFQN())
.mapKey(instruction -> c)
.entries()))
.forEachOrdered((c, a) -> c.annotations(apiAnnotation.getBinary())
.map(ann -> API.Status.valueOf(ann.get(STATUS_PROPERTY)))
.max(API.Status::compareTo)
.ifPresent(status -> exportedByAPIGuardian.computeIfAbsent(c.getClassName()
.getPackageRef()
.getFQN(), k -> new Attrs(a))
.compute(STATUS_PROPERTY, (k, v) -> (v == null) ? status.name()
: (API.Status.valueOf(v)
.compareTo(status) > 0 ? v : status.name()))));
exportedByAPIGuardian.values()
.stream()
.filter(attrs -> API.Status.valueOf(attrs.get(STATUS_PROPERTY)) == API.Status.INTERNAL)
.forEach(attrs -> {
attrs.put(Constants.MANDATORY_DIRECTIVE, STATUS_PROPERTY);
attrs.put(Constants.NO_IMPORT_DIRECTIVE, "true");
});
exportedByAnnotation.mergeWith(exportedByAPIGuardian, false);
return exportedByAnnotation;
}
// Handle org.osgi.annotation.bundle.Referenced annotation
private Set referencesByAnnotation(Clazz clazz) {
TypeRef referencedAnnotation = descriptors.getTypeRef("org/osgi/annotation/bundle/Referenced");
if (clazz.annotations()
.contains(referencedAnnotation)) {
Set referenced = clazz.annotations(referencedAnnotation.getBinary())
.flatMap(ann -> ann.stream("value", TypeRef.class))
.map(TypeRef::getPackageRef)
.collect(toSet());
return referenced;
}
return Collections.emptySet();
}
public Clazz getPackageInfo(PackageRef packageRef) {
TypeRef tr = descriptors.getPackageInfo(packageRef);
try {
return findClass(tr);
} catch (Exception e) {
return null;
}
}
private void doConditionalPackages() throws Exception {
//
// We need to find out the contained packages
// again ... so we need to clear any visited
// packages otherwise new packages are not
// added to contained
//
packagesVisited.clear();
for (Jar extra; (extra = getExtra()) != null;) {
dot.addAll(extra);
analyzeJar(extra, "", true, null, false);
}
}
/*
* Learn the package details from the Jar. This can either be the manifest
* (in that case the attrs are already set on the package), a
* package-info.java file, or a packageinfo file (in the given priority).
* This method will only learn a package once, so this method an be called
* for every class. It will also ignore any metadata or java packages.
*/
private void learnPackage(Jar jar, String prefix, PackageRef packageRef, Packages map) throws Exception {
if (packageRef.isMetaData() || packageRef.isJava() || packageRef.isPrimitivePackage())
return;
//
// We should ignore empty directories/packages. Empty packages should
// not take the package slot. See #708
//
String packagePath = appendPath(prefix, packageRef.getBinary());
Map dir = jar.getDirectory(packagePath);
if (dir == null || dir.isEmpty())
return;
//
// Make sure we only do this once for each package
// in cp order (which is the calling order)
//
if (packagesVisited.contains(packageRef))
return;
packagesVisited.add(packageRef);
//
// The manifest has priority over the packageinfo or package-info.java
// so if any user defined attributes are set, skip this package info
//
Attrs attrs = map.get(packageRef);
if (attrs != null && !attrs.select(AttributeClasses.MANIFEST)
.isEmpty())
return;
String jarname = map.getOrDefault(packageRef, Attrs.EMPTY_ATTRS)
.getOrDefault(INTERNAL_SOURCE_DIRECTIVE, getName(jar));
Resource resource = jar.getResource(packagePath.concat("/package-info.class"));
if (resource != null) {
Attrs info = parsePackageInfoClass(resource);
if (info != null && info.containsKey(VERSION_ATTRIBUTE)) {
info.put(FROM_DIRECTIVE, resource.toString());
info.put(INTERNAL_SOURCE_DIRECTIVE, jarname);
map.put(packageRef, info);
return;
}
}
resource = jar.getResource(packagePath.concat("/packageinfo"));
if (resource != null) {
Attrs info = parsePackageinfo(packageRef, resource);
if (info != null) {
info.put(FROM_DIRECTIVE, resource.toString());
info.putIfAbsent(INTERNAL_SOURCE_DIRECTIVE, getName(jar));
info.put(INTERNAL_SOURCE_DIRECTIVE, jarname);
fixupOldStyleVersions(info);
map.put(packageRef, info);
return;
}
}
//
// We need to set an attribute because this is how the set
// of available packages is remembered
//
map.put(packageRef)
.put(INTERNAL_SOURCE_DIRECTIVE, jarname);
// trace("%s from %s has no package info (either manifest, packageinfo
// or package-info.class",
// packageRef, jar);
}
protected String getName(Jar jar) throws Exception {
String name = jar.getBsn();
if (name == null) {
name = jar.getName();
if (name.equals("dot") && jar.getSource() != null)
name = jar.getSource()
.getName();
}
String version = jar.getVersion();
if (version == null)
version = "0.0.0";
return name + "-" + version;
}
/*
* Helper method to set the package info resource
*/
final static Pattern OLD_PACKAGEINFO_SYNTAX_P = Pattern
.compile("class\\s+(.+)\\s+version\\s+(" + Verifier.VERSION_S + ")");
Attrs parsePackageinfo(PackageRef packageRef, Resource r) throws Exception {
Properties p = new UTF8Properties();
try {
try (InputStream in = r.openInputStream()) {
p.load(in);
}
Attrs attrs = new Attrs();
for (String key : Iterables.iterable(p.propertyNames(), String.class::cast)) {
String propvalue = p.getProperty(key);
if (key.equalsIgnoreCase("include")) {
// Ouch, could be really old syntax
// We ignore the include part upto we
// ignored it long before.
Matcher m = OLD_PACKAGEINFO_SYNTAX_P.matcher(propvalue);
if (m.matches()) {
key = Constants.VERSION_ATTRIBUTE;
propvalue = m.group(2);
if (isPedantic())
warning("found old syntax in package info in package %s, from resource %s", packageRef, r);
}
}
String value = propvalue;
// Messy, to allow directives we need to
// allow the value to start with a ':' upto we cannot
// encode this in a property name
if (value.startsWith(":")) {
key = key + ":";
value = value.substring(1);
}
attrs.put(key, value);
}
return attrs;
} catch (Exception e) {
msgs.NoSuchFile_(r);
}
return null;
}
/*
* Parse the package-info.java class
*/
final static Pattern OBJECT_REFERENCE = Pattern.compile("([^.]+\\.)*([^.]+)");
private Attrs parsePackageInfoClass(Resource r) throws Exception {
final Attrs info = new Attrs();
final Clazz clazz = new Clazz(this, "", r);
clazz.parseClassFileWithCollector(new ClassDataCollector() {
@Override
public void annotation(Annotation a) {
String name = a.getName()
.getFQN();
switch (name) {
case "org.osgi.annotation.versioning.Version" :
// Check version
String version = a.get("value");
if (!info.containsKey(Constants.VERSION_ATTRIBUTE)) {
if (version != null) {
String processedVersion = getReplacer().process(version);
if (!Objects.equals(version, processedVersion)) {
warning("In class %s,"
+ " Version(\"%s\") does not have a literal version value and uses Bnd macros."
+ " This is highly unrecommended as it can produce results that vary depending upon the context in which it is evaluated.",
clazz, version);
version = processedVersion;
}
if (Verifier.VERSION.matcher(version)
.matches())
info.put(VERSION_ATTRIBUTE, version);
else
error("Version annotation in %s has invalid version info: %s", clazz, version);
}
} else {
// Verify this matches with packageinfo
String presentVersion = info.get(VERSION_ATTRIBUTE);
try {
Version av = new Version(presentVersion).getWithoutQualifier();
Version bv = new Version(version).getWithoutQualifier();
if (!av.equals(bv)) {
error("Version from annotation for %s differs with packageinfo or Manifest",
clazz.getClassName()
.getFQN());
}
} catch (Exception e) {
// Ignore
}
}
break;
case "org.osgi.annotation.versioning.ProviderType" :
if (!info.containsKey(PROVIDE_DIRECTIVE)) {
// let Export.substitution override ProviderType
info.put(PROVIDE_DIRECTIVE, "true");
}
break;
case "org.osgi.annotation.bundle.Export" :
Object[] usesClauses = a.get("uses");
if (usesClauses != null) {
StringJoiner sj = new StringJoiner(",");
String old = info.get(USES_DIRECTIVE);
if (old != null)
sj.add(old);
for (Object usesClause : usesClauses) {
sj.add(usesClause.toString());
}
info.put(USES_DIRECTIVE, sj.toString());
}
Object substitution = a.get("substitution");
if (substitution != null) {
switch (substitution.toString()
.toUpperCase(Locale.ROOT)) {
case "CONSUMER" :
info.put(PROVIDE_DIRECTIVE, "false");
break;
case "PROVIDER" :
info.put(PROVIDE_DIRECTIVE, "true");
break;
case "NOIMPORT" :
info.put(NO_IMPORT_DIRECTIVE, "true");
break;
case "CALCULATED" :
// nothing to do; this is the normal case
break;
default :
error("Export annotation in %s has invalid substitution value: %s", clazz,
substitution);
break;
}
}
Object[] attributes = a.get("attribute");
if (attributes != null) {
for (Object attribute : attributes) {
info.mergeWith(OSGiHeader.parseProperties(attribute.toString(), Analyzer.this), false);
}
}
break;
case "aQute.bnd.annotation.Export" :
// Check mandatory attributes
Attrs attrs = doAttrbutes(a.get(Export.MANDATORY), clazz, getReplacer());
if (!attrs.isEmpty()) {
info.putAll(attrs);
info.put(MANDATORY_DIRECTIVE, Processor.join(attrs.keySet()));
}
// Check optional attributes
attrs = doAttrbutes(a.get(Export.OPTIONAL), clazz, getReplacer());
if (!attrs.isEmpty()) {
info.putAll(attrs);
}
// Check Included classes
Object[] included = a.get(Export.INCLUDE);
if (included != null && included.length > 0) {
StringBuilder sb = new StringBuilder();
String del = "";
for (Object i : included) {
Matcher m = OBJECT_REFERENCE.matcher(((TypeRef) i).getFQN());
if (m.matches()) {
sb.append(del);
sb.append(m.group(2));
del = ",";
}
}
info.put(INCLUDE_DIRECTIVE, sb.toString());
}
// Check Excluded classes
Object[] excluded = a.get(Export.EXCLUDE);
if (excluded != null && excluded.length > 0) {
StringBuilder sb = new StringBuilder();
String del = "";
for (Object i : excluded) {
Matcher m = OBJECT_REFERENCE.matcher(((TypeRef) i).getFQN());
if (m.matches()) {
sb.append(del);
sb.append(m.group(2));
del = ",";
}
}
info.put(EXCLUDE_DIRECTIVE, sb.toString());
}
// Check Uses
Object[] uses = a.get(Export.USES);
if (uses != null && uses.length > 0) {
String old = info.get(USES_DIRECTIVE);
if (old == null)
old = "";
StringBuilder sb = new StringBuilder(old);
String del = sb.length() == 0 ? "" : ",";
for (Object use : uses) {
sb.append(del);
sb.append(use);
del = ",";
}
info.put(USES_DIRECTIVE, sb.toString());
}
break;
}
}
});
return info;
}
/**
* Discussed with BJ and decided to kill the .
*
* @param referredAndExported
*/
void removeDynamicImports(Packages referredAndExported) {
// // Remove any matching a dynamic import package instruction
// Instructions dynamicImports = new
// Instructions(getDynamicImportPackage());
// Collection dynamic = dynamicImports.select(
// referredAndExported.keySet(), false);
// referredAndExported.keySet().removeAll(dynamic);
}
protected Jar getExtra() throws Exception {
return null;
}
/*
* Call AnalyzerPlugins to analyze the content.
*/
private void doPlugins() {
doPlugins(AnalyzerPlugin.class, (plugin) -> {
boolean reanalyze;
Processor previous = beginHandleErrors(plugin.toString());
try {
reanalyze = plugin.analyzeJar(this);
} finally {
endHandleErrors(previous);
}
if (reanalyze) {
// Builder adds -internal-source information
Map sourceInformation = contained.stream()
.mapValue(attrs -> attrs.get(INTERNAL_SOURCE_DIRECTIVE))
.filterValue(Objects::nonNull)
.collect(MapStream.toMap());
reset();
analyzeContent();
// Restore -internal-source information
// if the package still exists
sourceInformation.forEach((pkgRef, source) -> {
Attrs attrs = contained.get(pkgRef);
if (attrs != null) {
attrs.put(INTERNAL_SOURCE_DIRECTIVE, source);
}
});
}
});
}
/*
* Call plugins to analyze the content.
*/
private void doPlugins(Class type, ConsumerWithException cb) {
List plugins = getPlugins(type);
plugins.sort(Comparator.comparingInt(OrderedPlugin::ordering));
for (T plugin : plugins) {
try {
cb.accept(plugin);
} catch (Exception e) {
exception(e, "Analyzer Plugin %s failed %s for %s", plugin, e, type.getSimpleName());
}
}
}
/**
* @return {@code true} if the {@code -resourceonly} instruction is set,
* {@code false} otherwise
*/
boolean isResourceOnly() {
return isTrue(getProperty(RESOURCEONLY));
}
/**
* One of the main workhorses of this class. This will analyze the current
* setup and calculate a new manifest according to this setup.
*
* @throws IOException
*/
public Manifest calcManifest() throws Exception {
return bracketed(this::calcManifest0);
}
private Manifest calcManifest0() throws Exception {
try {
analyze();
Manifest manifest = new Manifest();
Attributes main = manifest.getMainAttributes();
main.put(Attributes.Name.MANIFEST_VERSION, "1.0");
main.putValue(BUNDLE_MANIFESTVERSION, "2");
boolean noExtraHeaders = "true".equalsIgnoreCase(getProperty(NOEXTRAHEADERS));
if (!noExtraHeaders) {
main.putValue(CREATED_BY,
System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")");
main.putValue(TOOL, "Bnd-" + getBndVersion());
if (!dot.isReproducible()) {
main.putValue(BND_LASTMODIFIED, Long.toString(System.currentTimeMillis()));
}
}
String exportHeader = printClauses(exports, true);
if (exportHeader.length() > 0)
main.putValue(EXPORT_PACKAGE, exportHeader);
else
main.remove(new Name(EXPORT_PACKAGE));
// Divide imports with resolution:=dynamic to DynamicImport-Package
// and add them to the existing DynamicImport-Package instruction
Pair regularAndDynamicImports = divideRegularAndDynamicImports();
Packages regularImports = regularAndDynamicImports.getFirst();
if (!regularImports.isEmpty()) {
main.putValue(IMPORT_PACKAGE, printClauses(regularImports));
} else {
main.remove(new Name(IMPORT_PACKAGE));
}
Parameters dynamicImports = regularAndDynamicImports.getSecond();
if (!dynamicImports.isEmpty()) {
main.putValue(DYNAMICIMPORT_PACKAGE, printClauses(dynamicImports));
} else {
main.remove(new Name(DYNAMICIMPORT_PACKAGE));
}
Packages temp = new Packages(contained);
temp.keySet()
.removeAll(exports.keySet());
//
// This actually can contain file names if the look
// like packages. So remove anything that maps
// to a resource in the JAR
//
temp.keySet()
.removeIf(packageRef -> dot.getResource(packageRef.getBinary()) != null);
if (!temp.isEmpty())
main.putValue(PRIVATE_PACKAGE, printClauses(temp));
else
main.remove(new Name(PRIVATE_PACKAGE));
Parameters bcp = getBundleClasspath();
if (bcp.isEmpty() || (bcp.containsKey(".") && bcp.size() == 1))
main.remove(new Name(BUNDLE_CLASSPATH));
else
main.putValue(BUNDLE_CLASSPATH, printClauses(bcp));
// ----- Require/Capabilities section
Parameters requirements = new Parameters(annotationHeaders.getHeader(REQUIRE_CAPABILITY), this, true);
Parameters capabilities = new Parameters(annotationHeaders.getHeader(PROVIDE_CAPABILITY), this, true);
//
// Do any contracts contracts
//
contracts.addToRequirements(requirements);
//
// We want to add the minimum EE as a requirement
// based on the class version
//
if (!isTrue(getProperty(NOEE)) //
&& !ees.isEmpty() // no use otherwise
&& since(About._2_3) // we want people to not have to
// automatically add it
&& !requirements.containsKey(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE) // and
// it
// should
// not
// be
// there
// already
) {
JAVA highest = ees.last();
Attrs attrs = new Attrs();
String filter = doEEProfiles(highest);
attrs.put(Constants.FILTER_DIRECTIVE, filter);
//
// Java 1.8 introduced profiles.
// If -eeprofile= auto | (="...")+ is set then
// we add a
requirements.add(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, attrs);
}
if (!requirements.isEmpty())
main.putValue(REQUIRE_CAPABILITY, requirements.toString());
if (!capabilities.isEmpty())
main.putValue(PROVIDE_CAPABILITY, capabilities.toString());
for (String header : Iterables.iterable(getProperties().propertyNames(), String.class::cast)) {
if (header.trim()
.length() == 0) {
warning("Empty property set with value: %s", getProperties().getProperty(header));
continue;
}
if (isMissingPlugin(header.trim())) {
error("Missing plugin for command %s", header);
}
if (!Character.isUpperCase(header.charAt(0))) {
if (header.charAt(0) == '@')
doNameSection(manifest, header);
continue;
}
if (header.equals(BUNDLE_CLASSPATH) || header.equals(EXPORT_PACKAGE) || header.equals(IMPORT_PACKAGE)
|| header.equals(DYNAMICIMPORT_PACKAGE) || header.equals(REQUIRE_CAPABILITY)
|| header.equals(PROVIDE_CAPABILITY))
continue;
if (header.equalsIgnoreCase("Name")) {
error(
"Your bnd file contains a header called 'Name'. This interferes with the manifest name section.");
continue;
}
if (Verifier.HEADER_PATTERN.matcher(header)
.matches()) {
doHeader(main, header);
} else {
// TODO should we report?
}
}
// Copy old values into new manifest, when they
// exist in the old one, but not in the new one
merge(manifest, dot.getManifest());
//
// Calculate the bundle symbolic name if it is
// not set.
// 1. set
// 2. name of properties file (must be != bnd.bnd)
// 3. name of directory, which is usualy project name
//
String bsn = getBsn();
if (main.getValue(BUNDLE_SYMBOLICNAME) == null) {
main.putValue(BUNDLE_SYMBOLICNAME, bsn);
}
//
// Use the same name for the bundle name as BSN when
// the bundle name is not set
//
if (main.getValue(BUNDLE_NAME) == null) {
main.putValue(BUNDLE_NAME, bsn);
}
if (main.getValue(BUNDLE_VERSION) == null)
main.putValue(BUNDLE_VERSION, "0");
// Remove all the headers mentioned in -removeheaders
Instructions instructions = new Instructions(mergeProperties(REMOVEHEADERS));
Collection