-contract
Description: package aQute.bnd.osgi; import java.util.*; import java.util.Map.Entry; import org.osgi.namespace.contract.*; import aQute.bnd.header.*; import aQute.bnd.osgi.Descriptors.PackageRef; import aQute.bnd.version.*; import aQute.lib.collections.*; import aQute.service.reporter.Report.Location; /** * OSGi Contracts are first defined in OSGi Enterprise Release 5.0.0. A Contract * is a namespace to control the versioning of a set of packages. * * @author aqute */ class Contracts { private Analyzer analyzer; private final MultiMap<PackageRef,Contract> contracted = new MultiMap<PackageRef,Contract>( PackageRef.class, Contract.class, true); private MultiMap<Collection<Contract>,PackageRef> overlappingContracts = new MultiMap<Collection<Contract>,PackageRef>(); private Instructions instructions; private final Set<Contract> contracts = new HashSet<Contract>(); public class Contract { public String name; public Attrs decorators; public Collection<String> uses; public Version version; public String f [cell cut: 5021 characters, first 1000 kept]

- Supported by OSGi enRoute

- # -contract

      	package aQute.bnd.osgi;

      	import java.util.*;
      	import java.util.Map.Entry;

      	import org.osgi.namespace.contract.*;

      	import aQute.bnd.header.*;
      	import aQute.bnd.osgi.Descriptors.PackageRef;
      	import aQute.bnd.version.*;
      	import aQute.lib.collections.*;
      	import aQute.service.reporter.Report.Location;

      	/**
      	 * OSGi Contracts are first defined in OSGi Enterprise Release 5.0.0. A Contract
      	 * is a namespace to control the versioning of a set of packages.
      	 *
      	 * @author aqute
      	 */
      	class Contracts {

      		private Analyzer									analyzer;
      		private final MultiMap<PackageRef,Contract>			contracted				= new MultiMap<PackageRef,Contract>(
      																							PackageRef.class,
      																							Contract.class, true);
      		private MultiMap<Collection<Contract>,PackageRef>	overlappingContracts	= new MultiMap<Collection<Contract>,PackageRef>();
      		private Instructions								instructions;
      		private final Set<Contract>							contracts				= new HashSet<Contract>();

      		public class Contract {
      			public String				name;
      			public Attrs				decorators;
      			public Collection<String>	uses;
      			public Version				version;
      			public String				from;

      			@Override
      			public String toString() {
      				return "Contract [name=" + name + ";version=" + version + ";from=" + from + "]";
      			}

      		}

      		public Contracts(Analyzer analyzer) {
      			this.analyzer = analyzer;
      		}

      		Instructions getFilter() {
      			if (instructions == null) {
      				String contract = analyzer.getProperty(Constants.CONTRACT);
      				this.instructions = new Instructions(contract);
      			}
      			return instructions;
      		}

      		public void clear() {
      			contracted.clear();
      			overlappingContracts.clear();
      			contracts.clear();
      		}

      		/**
      		 * Collect contracts will take a domain and find any declared contracts.
      		 * This happens early so that we have a list of contracts we can later
      		 * compare the imports against.
      		 */
      		void collectContracts(String from, Parameters pcs) {

      			contract: for (Entry<String,Attrs> p : pcs.entrySet()) {
      				String namespace = p.getKey();

      				if (namespace.equals(ContractNamespace.CONTRACT_NAMESPACE)) {
      					Attrs capabilityAttrs = p.getValue();

      					String name = capabilityAttrs.get(ContractNamespace.CONTRACT_NAMESPACE);
      					if (name == null) {
      						analyzer.warning("No name (attr %s) defined in bundle %s from contract namespace: %s",
      								ContractNamespace.CONTRACT_NAMESPACE, from, capabilityAttrs);
      						continue contract;
      					}

      					for (Entry<Instruction,Attrs> i : getFilter().entrySet()) {
      						Instruction instruction = i.getKey();
      						if (instruction.matches(name)) {
      							if (instruction.isNegated()) {
      								analyzer.trace("%s rejected due to %s", namespace, instructions);
      								continue contract;
      							}

      							analyzer.trace("accepted %s", p);

      							Contract c = new Contract();
      							c.name = name;

      							String list = capabilityAttrs.get(ContractNamespace.CAPABILITY_USES_DIRECTIVE + ":");
      							if (list == null || list.length() == 0) {
      								analyzer.warning("Contract %s has no uses: directive in %s.", name, from);
      								continue contract; // next contract
      							}

      							c.uses = Processor.split(list);

      							c.version = (Version) capabilityAttrs.getTyped(ContractNamespace.CAPABILITY_VERSION_ATTRIBUTE);
      							c.from = from;

      							if (c.version == null) {
      								c.version = Version.LOWEST;
      								analyzer.warning("%s does not declare a version, assumed 0.0.0.", c);
      							}
      							c.decorators = new Attrs(i.getValue());

      							//
      							// Build up the package -> contract index
      							//
      							for (String pname : c.uses) {
      								contracted.add(analyzer.getPackageRef(pname), c);
      							}
      						}
      					}
      				}
      			}
      		}

      		/**
      		 * Find out if a package is contracted. If there are multiple contracts for
      		 * a package we remember this so we can generate a single error.
      		 *
      		 * @param packageRef
      		 * @return
      		 */
      		boolean isContracted(PackageRef packageRef) {
      			List<Contract> list = contracted.get(packageRef);
      			if (list == null || list.isEmpty())
      				return false;

      			if (list.size() > 1) {

      				//
      				// There are multiple contracts trying to address
      				// this package. We collect those so we can report them
      				// as one error instead of one for each package
      				//

      				overlappingContracts.add(list, packageRef);
      			}
      			contracts.addAll(list);
      			return true;
      		}

      		/**
      		 * Called before we print the manifest. Should add any contracts that were
      		 * actually used to the requirements.
      		 *
      		 * @param requirements
      		 */
      		void addToRequirements(Parameters requirements) {
      			for (Contract c : contracts) {
      				Attrs attrs = new Attrs(c.decorators);
      				attrs.put(ContractNamespace.CONTRACT_NAMESPACE, c.name);
      				String range = analyzer.applyVersionPolicy(c.version.toString(), c.decorators.getVersion(), false);
      				String name = ContractNamespace.CONTRACT_NAMESPACE;
      				while (requirements.containsKey(name))
      					name += "~";

      				VersionRange r = new VersionRange(range);

      				Formatter f = new Formatter();
      				try {
      					f.format("(&(%s=%s)%s)", ContractNamespace.CONTRACT_NAMESPACE, c.name, r.toFilter());

      					// TODO : shall we also assert the attributes?

      					attrs.put("filter:", f.toString());

      					requirements.put(name, attrs);
      				}
      				finally {
      					f.close();
      				}
      			}

      			for (Entry<Collection<Contract>,List<PackageRef>> oc : overlappingContracts.entrySet()) {
      				Location location = analyzer
      						.error("Contracts %s declare the same packages in their uses: directive: %s. "
      								+ "Contracts are found in declaring bundles (see their 'from' field), it is possible to control the finding"
      								+ "with the -contract instruction", oc.getKey(), oc.getValue()).location();
      				location.header = Constants.CONTRACT;
      			}
      		}

      	}