You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2014/04/11 19:20:50 UTC
[28/33] Revert "[KARAF-2852] Merge features/core and features/command"
http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
new file mode 100644
index 0000000..cb2c36a
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
@@ -0,0 +1,1129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.resolver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.utils.version.VersionRange;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class ResourceBuilder {
+
+ public static final String RESOLUTION_DYNAMIC = "dynamic";
+
+ public static Resource build(String uri, Map<String, String> headerMap)
+ throws BundleException {
+
+ // Verify that only manifest version 2 is specified.
+ String manifestVersion = getManifestVersion(headerMap);
+ if (manifestVersion == null || !manifestVersion.equals("2")) {
+ throw new BundleException("Unsupported 'Bundle-ManifestVersion' value: " + manifestVersion);
+ }
+
+ //
+ // Parse bundle version.
+ //
+
+ Version bundleVersion = Version.emptyVersion;
+ if (headerMap.get(Constants.BUNDLE_VERSION) != null) {
+ bundleVersion = Version.parseVersion(headerMap.get(Constants.BUNDLE_VERSION));
+ }
+
+ //
+ // Parse bundle symbolic name.
+ //
+
+ String bundleSymbolicName = null;
+ ParsedHeaderClause bundleCap = parseBundleSymbolicName(headerMap);
+ if (bundleCap == null) {
+ throw new BundleException("Bundle manifest must include bundle symbolic name");
+ }
+ bundleSymbolicName = (String) bundleCap.attrs.get(BundleRevision.BUNDLE_NAMESPACE);
+
+ // Now that we have symbolic name and version, create the resource
+ String type = headerMap.get(Constants.FRAGMENT_HOST) == null ? IdentityNamespace.TYPE_BUNDLE : IdentityNamespace.TYPE_FRAGMENT;
+ ResourceImpl resource = new ResourceImpl(bundleSymbolicName, type, bundleVersion);
+ if (uri != null) {
+ Map<String, Object> attrs = new HashMap<String, Object>();
+ attrs.put(UriNamespace.URI_NAMESPACE, uri);
+ resource.addCapability(new CapabilityImpl(resource, UriNamespace.URI_NAMESPACE, Collections.<String, String>emptyMap(), attrs));
+ }
+
+ // Add a bundle and host capability to all
+ // non-fragment bundles. A host capability is the same
+ // as a require capability, but with a different capability
+ // namespace. Bundle capabilities resolve required-bundle
+ // dependencies, while host capabilities resolve fragment-host
+ // dependencies.
+ if (headerMap.get(Constants.FRAGMENT_HOST) == null) {
+ // All non-fragment bundles have bundle capability.
+ resource.addCapability(new CapabilityImpl(resource, BundleRevision.BUNDLE_NAMESPACE, bundleCap.dirs, bundleCap.attrs));
+ // A non-fragment bundle can choose to not have a host capability.
+ String attachment = bundleCap.dirs.get(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE);
+ attachment = (attachment == null) ? Constants.FRAGMENT_ATTACHMENT_RESOLVETIME : attachment;
+ if (!attachment.equalsIgnoreCase(Constants.FRAGMENT_ATTACHMENT_NEVER)) {
+ Map<String, Object> hostAttrs = new HashMap<String, Object>(bundleCap.attrs);
+ Object value = hostAttrs.remove(BundleRevision.BUNDLE_NAMESPACE);
+ hostAttrs.put(BundleRevision.HOST_NAMESPACE, value);
+ resource.addCapability(new CapabilityImpl(
+ resource, BundleRevision.HOST_NAMESPACE,
+ bundleCap.dirs,
+ hostAttrs));
+ }
+ }
+
+ //
+ // Parse Fragment-Host.
+ //
+
+ List<RequirementImpl> hostReqs = parseFragmentHost(resource, headerMap);
+
+ //
+ // Parse Require-Bundle
+ //
+
+ List<ParsedHeaderClause> rbClauses = parseStandardHeader(headerMap.get(Constants.REQUIRE_BUNDLE));
+ rbClauses = normalizeRequireClauses(rbClauses);
+ List<Requirement> rbReqs = convertRequires(rbClauses, resource);
+
+ //
+ // Parse Import-Package.
+ //
+
+ List<ParsedHeaderClause> importClauses = parseStandardHeader(headerMap.get(Constants.IMPORT_PACKAGE));
+ importClauses = normalizeImportClauses(importClauses);
+ List<Requirement> importReqs = convertImports(importClauses, resource);
+
+ //
+ // Parse DynamicImport-Package.
+ //
+
+ List<ParsedHeaderClause> dynamicClauses = parseStandardHeader(headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
+ dynamicClauses = normalizeDynamicImportClauses(dynamicClauses);
+ List<Requirement> dynamicReqs = convertImports(dynamicClauses, resource);
+
+ //
+ // Parse Require-Capability.
+ //
+
+ List<ParsedHeaderClause> requireClauses = parseStandardHeader(headerMap.get(Constants.REQUIRE_CAPABILITY));
+ requireClauses = normalizeRequireCapabilityClauses(requireClauses);
+ List<Requirement> requireReqs = convertRequireCapabilities(requireClauses, resource);
+
+ //
+ // Parse Export-Package.
+ //
+
+ List<ParsedHeaderClause> exportClauses = parseStandardHeader(headerMap.get(Constants.EXPORT_PACKAGE));
+ exportClauses = normalizeExportClauses(exportClauses, bundleSymbolicName, bundleVersion);
+ List<Capability> exportCaps = convertExports(exportClauses, resource);
+
+ //
+ // Parse Provide-Capability.
+ //
+
+ List<ParsedHeaderClause> provideClauses = parseStandardHeader(headerMap.get(Constants.PROVIDE_CAPABILITY));
+ provideClauses = normalizeProvideCapabilityClauses(provideClauses);
+ List<Capability> provideCaps = convertProvideCapabilities(provideClauses, resource);
+
+ //
+ // Parse Import-Service and Export-Service
+ // if Require-Capability and Provide-Capability are not set for services
+ //
+
+ boolean hasServiceReferenceCapability = false;
+ for (Capability cap : exportCaps) {
+ hasServiceReferenceCapability |= ServiceNamespace.SERVICE_NAMESPACE.equals(cap.getNamespace());
+ }
+ if (!hasServiceReferenceCapability) {
+ List<ParsedHeaderClause> exportServices = parseStandardHeader(headerMap.get(Constants.EXPORT_SERVICE));
+ List<Capability> caps = convertExportService(exportServices, resource);
+ provideCaps.addAll(caps);
+ }
+
+ boolean hasServiceReferenceRequirement = false;
+ for (Requirement req : requireReqs) {
+ hasServiceReferenceRequirement |= ServiceNamespace.SERVICE_NAMESPACE.equals(req.getNamespace());
+ }
+ if (!hasServiceReferenceRequirement) {
+ List<ParsedHeaderClause> importServices = parseStandardHeader(headerMap.get(Constants.IMPORT_SERVICE));
+ List<Requirement> reqs = convertImportService(importServices, resource);
+ requireReqs.addAll(reqs);
+ }
+
+ // Combine all capabilities.
+ resource.addCapabilities(exportCaps);
+ resource.addCapabilities(provideCaps);
+
+ // Combine all requirements.
+ resource.addRequirements(hostReqs);
+ resource.addRequirements(importReqs);
+ resource.addRequirements(rbReqs);
+ resource.addRequirements(requireReqs);
+ resource.addRequirements(dynamicReqs);
+
+ return resource;
+ }
+
+ public static List<Requirement> parseImport(Resource resource, String imports) throws BundleException {
+ List<ParsedHeaderClause> importClauses = parseStandardHeader(imports);
+ importClauses = normalizeImportClauses(importClauses);
+ List<Requirement> importReqs = convertImports(importClauses, resource);
+ return importReqs;
+ }
+
+ public static List<Requirement> parseRequirement(Resource resource, String requirement) throws BundleException {
+ List<ParsedHeaderClause> requireClauses = parseStandardHeader(requirement);
+ requireClauses = normalizeRequireCapabilityClauses(requireClauses);
+ List<Requirement> requireReqs = convertRequireCapabilities(requireClauses, resource);
+ return requireReqs;
+ }
+
+ public static List<Capability> parseExport(Resource resource, String bundleSymbolicName, Version bundleVersion, String exports) throws BundleException {
+ List<ParsedHeaderClause> exportClauses = parseStandardHeader(exports);
+ exportClauses = normalizeExportClauses(exportClauses, bundleSymbolicName, bundleVersion);
+ List<Capability> exportCaps = convertExports(exportClauses, resource);
+ return exportCaps;
+ }
+
+ public static List<Capability> parseCapability(Resource resource, String capability) throws BundleException {
+ List<ParsedHeaderClause> provideClauses = parseStandardHeader(capability);
+ provideClauses = normalizeProvideCapabilityClauses(provideClauses);
+ List<Capability> provideCaps = convertProvideCapabilities(provideClauses, resource);
+ return provideCaps;
+ }
+
+ @SuppressWarnings( "deprecation" )
+ private static List<ParsedHeaderClause> normalizeImportClauses(
+ List<ParsedHeaderClause> clauses)
+ throws BundleException {
+ // Verify that the values are equals if the package specifies
+ // both version and specification-version attributes.
+ Set<String> dupeSet = new HashSet<String>();
+ for (ParsedHeaderClause clause : clauses) {
+ // Check for "version" and "specification-version" attributes
+ // and verify they are the same if both are specified.
+ Object v = clause.attrs.get(Constants.VERSION_ATTRIBUTE);
+ Object sv = clause.attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+ if ((v != null) && (sv != null)) {
+ // Verify they are equal.
+ if (!((String) v).trim().equals(((String) sv).trim())) {
+ throw new IllegalArgumentException(
+ "Both version and specification-version are specified, but they are not equal.");
+ }
+ }
+
+ // Ensure that only the "version" attribute is used and convert
+ // it to the VersionRange type.
+ if ((v != null) || (sv != null)) {
+ clause.attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+ v = (v == null) ? sv : v;
+ clause.attrs.put(Constants.VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+ }
+
+ // If bundle version is specified, then convert its type to VersionRange.
+ v = clause.attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+ if (v != null) {
+ clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+ }
+
+ // Verify java.* is not imported, nor any duplicate imports.
+ for (String pkgName : clause.paths) {
+ if (!dupeSet.contains(pkgName)) {
+ // Verify that java.* packages are not imported.
+ if (pkgName.startsWith("java.")) {
+ throw new BundleException("Importing java.* packages not allowed: " + pkgName);
+ }
+ // The character "." has no meaning in the OSGi spec except
+ // when placed on the bundle class path. Some people, however,
+ // mistakenly think it means the default package when imported
+ // or exported. This is not correct. It is invalid.
+ else if (pkgName.equals(".")) {
+ throw new BundleException("Importing '.' is invalid.");
+ }
+ // Make sure a package name was specified.
+ else if (pkgName.length() == 0) {
+ throw new BundleException(
+ "Imported package names cannot be zero length.");
+ }
+ dupeSet.add(pkgName);
+ } else {
+ throw new BundleException("Duplicate import: " + pkgName);
+ }
+ }
+ }
+
+ return clauses;
+ }
+
+ private static List<Capability> convertExportService(List<ParsedHeaderClause> clauses, Resource resource) {
+ List<Capability> capList = new ArrayList<Capability>();
+ for (ParsedHeaderClause clause : clauses) {
+ for (String path : clause.paths) {
+ Map<String, String> dirs = new LinkedHashMap<String, String>();
+ dirs.put(ServiceNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE, ServiceNamespace.EFFECTIVE_ACTIVE);
+ Map<String, Object> attrs = new LinkedHashMap<String, Object>();
+ attrs.put(Constants.OBJECTCLASS, path);
+ attrs.putAll(clause.attrs);
+ capList.add(new CapabilityImpl(
+ resource,
+ ServiceNamespace.SERVICE_NAMESPACE,
+ dirs,
+ attrs));
+ }
+ }
+ return capList;
+ }
+
+ private static List<Requirement> convertImportService(List<ParsedHeaderClause> clauses, Resource resource) throws BundleException {
+ try {
+ List<Requirement> reqList = new ArrayList<Requirement>();
+ for (ParsedHeaderClause clause : clauses) {
+ for (String path : clause.paths) {
+ String multiple = clause.dirs.get("multiple");
+ String avail = clause.dirs.get("availability");
+ String filter = (String) clause.attrs.get("filter");
+ Map<String, String> dirs = new LinkedHashMap<String, String>();
+ dirs.put(ServiceNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE, ServiceNamespace.EFFECTIVE_ACTIVE);
+ if ("optional".equals(avail)) {
+ dirs.put(ServiceNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE, ServiceNamespace.RESOLUTION_OPTIONAL);
+ }
+ if ("true".equals(multiple)) {
+ dirs.put(ServiceNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE, ServiceNamespace.CARDINALITY_MULTIPLE);
+ }
+ if (filter == null) {
+ filter = "(" + Constants.OBJECTCLASS + "=" + path + ")";
+ } else if (!filter.startsWith("(") && !filter.endsWith(")")) {
+ filter = "(&(" + Constants.OBJECTCLASS + "=" + path + ")(" + filter + "))";
+ } else {
+ filter = "(&(" + Constants.OBJECTCLASS + "=" + path + ")" + filter + ")";
+ }
+ dirs.put(ServiceNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
+ reqList.add(new RequirementImpl(
+ resource,
+ ServiceNamespace.SERVICE_NAMESPACE,
+ dirs,
+ Collections.<String, Object>emptyMap(),
+ SimpleFilter.parse(filter)));
+ }
+ }
+ return reqList;
+ } catch (Exception ex) {
+ throw new BundleException("Error creating requirement: " + ex, ex);
+ }
+ }
+
+ private static List<Requirement> convertImports(List<ParsedHeaderClause> clauses, Resource resource) {
+ // Now convert generic header clauses into requirements.
+ List<Requirement> reqList = new ArrayList<Requirement>();
+ for (ParsedHeaderClause clause : clauses) {
+ for (String path : clause.paths) {
+ // Prepend the package name to the array of attributes.
+ Map<String, Object> attrs = clause.attrs;
+ // Note that we use a linked hash map here to ensure the
+ // package attribute is first, which will make indexing
+ // more efficient.
+ // TODO: OSGi R4.3 - This is ordering is kind of hacky.
+ // Prepend the package name to the array of attributes.
+ Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1);
+ // We want this first from an indexing perspective.
+ newAttrs.put(BundleRevision.PACKAGE_NAMESPACE, path);
+ newAttrs.putAll(attrs);
+ // But we need to put it again to make sure it wasn't overwritten.
+ newAttrs.put(BundleRevision.PACKAGE_NAMESPACE, path);
+
+ // Create filter now so we can inject filter directive.
+ SimpleFilter sf = SimpleFilter.convert(newAttrs);
+
+ // Inject filter directive.
+ // TODO: OSGi R4.3 - Can we insert this on demand somehow?
+ Map<String, String> dirs = clause.dirs;
+ Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1);
+ newDirs.putAll(dirs);
+ newDirs.put(Constants.FILTER_DIRECTIVE, sf.toString());
+
+ // Create package requirement and add to requirement list.
+ reqList.add(
+ new RequirementImpl(
+ resource,
+ BundleRevision.PACKAGE_NAMESPACE,
+ newDirs,
+ Collections.<String, Object>emptyMap(),
+ sf));
+ }
+ }
+
+ return reqList;
+ }
+
+ @SuppressWarnings( "deprecation" )
+ private static List<ParsedHeaderClause> normalizeDynamicImportClauses(
+ List<ParsedHeaderClause> clauses)
+ throws BundleException {
+ // Verify that the values are equals if the package specifies
+ // both version and specification-version attributes.
+ for (ParsedHeaderClause clause : clauses) {
+ // Add the resolution directive to indicate that these are
+ // dynamic imports.
+ clause.dirs.put(Constants.RESOLUTION_DIRECTIVE, RESOLUTION_DYNAMIC);
+
+ // Check for "version" and "specification-version" attributes
+ // and verify they are the same if both are specified.
+ Object v = clause.attrs.get(Constants.VERSION_ATTRIBUTE);
+ Object sv = clause.attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+ if ((v != null) && (sv != null)) {
+ // Verify they are equal.
+ if (!((String) v).trim().equals(((String) sv).trim())) {
+ throw new IllegalArgumentException(
+ "Both version and specification-version are specified, but they are not equal.");
+ }
+ }
+
+ // Ensure that only the "version" attribute is used and convert
+ // it to the VersionRange type.
+ if ((v != null) || (sv != null)) {
+ clause.attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+ v = (v == null) ? sv : v;
+ clause.attrs.put(Constants.VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+ }
+
+ // If bundle version is specified, then convert its type to VersionRange.
+ v = clause.attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+ if (v != null) {
+ clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, VersionRange.parseVersionRange(v.toString()));
+ }
+
+ // Dynamic imports can have duplicates, so verify that java.*
+ // packages are not imported.
+ for (String pkgName : clause.paths) {
+ if (pkgName.startsWith("java.")) {
+ throw new BundleException("Dynamically importing java.* packages not allowed: " + pkgName);
+ } else if (!pkgName.equals("*") && pkgName.endsWith("*") && !pkgName.endsWith(".*")) {
+ throw new BundleException("Partial package name wild carding is not allowed: " + pkgName);
+ }
+ }
+ }
+
+ return clauses;
+ }
+
+ private static List<ParsedHeaderClause> normalizeRequireCapabilityClauses(
+ List<ParsedHeaderClause> clauses)
+ throws BundleException {
+
+ return clauses;
+ }
+
+ private static List<ParsedHeaderClause> normalizeProvideCapabilityClauses(
+ List<ParsedHeaderClause> clauses)
+ throws BundleException
+ {
+
+ // Convert attributes into specified types.
+ for (ParsedHeaderClause clause : clauses)
+ {
+ for (Map.Entry<String, String> entry : clause.types.entrySet())
+ {
+ String type = entry.getValue();
+ if (!type.equals("String"))
+ {
+ if (type.equals("Double"))
+ {
+ clause.attrs.put(
+ entry.getKey(),
+ new Double(clause.attrs.get(entry.getKey()).toString().trim()));
+ }
+ else if (type.equals("Version"))
+ {
+ clause.attrs.put(
+ entry.getKey(),
+ new Version(clause.attrs.get(entry.getKey()).toString().trim()));
+ }
+ else if (type.equals("Long"))
+ {
+ clause.attrs.put(
+ entry.getKey(),
+ new Long(clause.attrs.get(entry.getKey()).toString().trim()));
+ }
+ else if (type.startsWith("List"))
+ {
+ int startIdx = type.indexOf('<');
+ int endIdx = type.indexOf('>');
+ if (((startIdx > 0) && (endIdx <= startIdx))
+ || ((startIdx < 0) && (endIdx > 0)))
+ {
+ throw new BundleException(
+ "Invalid Provide-Capability attribute list type for '"
+ + entry.getKey()
+ + "' : "
+ + type);
+ }
+
+ String listType = "String";
+ if (endIdx > startIdx)
+ {
+ listType = type.substring(startIdx + 1, endIdx).trim();
+ }
+
+ List<String> tokens = parseDelimitedString(
+ clause.attrs.get(entry.getKey()).toString(), ",", false);
+ List<Object> values = new ArrayList<Object>(tokens.size());
+ for (String token : tokens)
+ {
+ if (listType.equals("String"))
+ {
+ values.add(token);
+ }
+ else if (listType.equals("Double"))
+ {
+ values.add(new Double(token.trim()));
+ }
+ else if (listType.equals("Version"))
+ {
+ values.add(new Version(token.trim()));
+ }
+ else if (listType.equals("Long"))
+ {
+ values.add(new Long(token.trim()));
+ }
+ else
+ {
+ throw new BundleException(
+ "Unknown Provide-Capability attribute list type for '"
+ + entry.getKey()
+ + "' : "
+ + type);
+ }
+ }
+ clause.attrs.put(
+ entry.getKey(),
+ values);
+ }
+ else
+ {
+ throw new BundleException(
+ "Unknown Provide-Capability attribute type for '"
+ + entry.getKey()
+ + "' : "
+ + type);
+ }
+ }
+ }
+ }
+
+ return clauses;
+ }
+
+ private static List<Requirement> convertRequireCapabilities(
+ List<ParsedHeaderClause> clauses, Resource resource)
+ throws BundleException {
+ // Now convert generic header clauses into requirements.
+ List<Requirement> reqList = new ArrayList<Requirement>();
+ for (ParsedHeaderClause clause : clauses) {
+ try {
+ String filterStr = clause.dirs.get(Constants.FILTER_DIRECTIVE);
+ SimpleFilter sf = (filterStr != null)
+ ? SimpleFilter.parse(filterStr)
+ : new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+ for (String path : clause.paths) {
+ // Create requirement and add to requirement list.
+ reqList.add(new RequirementImpl(
+ resource, path, clause.dirs, clause.attrs, sf));
+ }
+ } catch (Exception ex) {
+ throw new BundleException("Error creating requirement: " + ex, ex);
+ }
+ }
+
+ return reqList;
+ }
+
+ private static List<Capability> convertProvideCapabilities(
+ List<ParsedHeaderClause> clauses, Resource resource)
+ throws BundleException {
+ List<Capability> capList = new ArrayList<Capability>();
+ for (ParsedHeaderClause clause : clauses) {
+ for (String path : clause.paths) {
+ if (path.startsWith("osgi.wiring.")) {
+// throw new BundleException("Manifest cannot use Provide-Capability for '" + path + "' namespace.");
+ }
+
+ // Create package capability and add to capability list.
+ capList.add(new CapabilityImpl(resource, path, clause.dirs, clause.attrs));
+ }
+ }
+
+ return capList;
+ }
+
+ @SuppressWarnings( "deprecation" )
+ private static List<ParsedHeaderClause> normalizeExportClauses(
+ List<ParsedHeaderClause> clauses,
+ String bsn, Version bv)
+ throws BundleException {
+ // Verify that "java.*" packages are not exported.
+ for (ParsedHeaderClause clause : clauses) {
+ // Verify that the named package has not already been declared.
+ for (String pkgName : clause.paths) {
+ // Verify that java.* packages are not exported.
+ if (pkgName.startsWith("java.")) {
+ throw new BundleException("Exporting java.* packages not allowed: " + pkgName);
+ }
+ // The character "." has no meaning in the OSGi spec except
+ // when placed on the bundle class path. Some people, however,
+ // mistakenly think it means the default package when imported
+ // or exported. This is not correct. It is invalid.
+ else if (pkgName.equals(".")) {
+ throw new BundleException("Exporing '.' is invalid.");
+ }
+ // Make sure a package name was specified.
+ else if (pkgName.length() == 0) {
+ throw new BundleException("Exported package names cannot be zero length.");
+ }
+ }
+
+ // Check for "version" and "specification-version" attributes
+ // and verify they are the same if both are specified.
+ Object v = clause.attrs.get(Constants.VERSION_ATTRIBUTE);
+ Object sv = clause.attrs.get(Constants.PACKAGE_SPECIFICATION_VERSION);
+ if ((v != null) && (sv != null)) {
+ // Verify they are equal.
+ if (!((String) v).trim().equals(((String) sv).trim())) {
+ throw new IllegalArgumentException("Both version and specification-version are specified, but they are not equal.");
+ }
+ }
+
+ // Always add the default version if not specified.
+ if ((v == null) && (sv == null)) {
+ v = Version.emptyVersion;
+ }
+
+ // Ensure that only the "version" attribute is used and convert
+ // it to the appropriate type.
+ if ((v != null) || (sv != null)) {
+ // Convert version attribute to type Version.
+ clause.attrs.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+ v = (v == null) ? sv : v;
+ clause.attrs.put(Constants.VERSION_ATTRIBUTE, Version.parseVersion(v.toString()));
+ }
+
+ // Find symbolic name and version attribute, if present.
+ if (clause.attrs.containsKey(Constants.BUNDLE_VERSION_ATTRIBUTE)
+ || clause.attrs.containsKey(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE)) {
+ throw new BundleException("Exports must not specify bundle symbolic name or bundle version.");
+ }
+
+ // Now that we know that there are no bundle symbolic name and version
+ // attributes, add them since the spec says they are there implicitly.
+ clause.attrs.put(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, bsn);
+ clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, bv);
+ }
+
+ return clauses;
+ }
+
+ private static List<Capability> convertExports(
+ List<ParsedHeaderClause> clauses, Resource resource) {
+ List<Capability> capList = new ArrayList<Capability>();
+ for (ParsedHeaderClause clause : clauses) {
+ for (String pkgName : clause.paths) {
+ // Prepend the package name to the array of attributes.
+ Map<String, Object> attrs = clause.attrs;
+ Map<String, Object> newAttrs = new HashMap<String, Object>(attrs.size() + 1);
+ newAttrs.putAll(attrs);
+ newAttrs.put(BundleRevision.PACKAGE_NAMESPACE, pkgName);
+
+ // Create package capability and add to capability list.
+ capList.add(new CapabilityImpl(resource, BundleRevision.PACKAGE_NAMESPACE, clause.dirs, newAttrs));
+ }
+ }
+
+ return capList;
+ }
+
+ private static String getManifestVersion(Map<String, String> headerMap) {
+ String manifestVersion = headerMap.get(Constants.BUNDLE_MANIFESTVERSION);
+ return (manifestVersion == null) ? "1" : manifestVersion.trim();
+ }
+
+ private static List<ParsedHeaderClause> calculateImplicitImports(
+ List<BundleCapability> exports, List<ParsedHeaderClause> imports)
+ throws BundleException {
+ List<ParsedHeaderClause> clauseList = new ArrayList<ParsedHeaderClause>();
+
+ // Since all R3 exports imply an import, add a corresponding
+ // requirement for each existing export capability. Do not
+ // duplicate imports.
+ Map<String, String> map = new HashMap<String, String>();
+ // Add existing imports.
+ for (ParsedHeaderClause anImport : imports) {
+ for (int pathIdx = 0; pathIdx < anImport.paths.size(); pathIdx++) {
+ map.put(anImport.paths.get(pathIdx), anImport.paths.get(pathIdx));
+ }
+ }
+ // Add import requirement for each export capability.
+ for (BundleCapability export : exports) {
+ if (map.get(export.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).toString()) == null) {
+ // Convert Version to VersionRange.
+ Object version = export.getAttributes().get(Constants.VERSION_ATTRIBUTE);
+ ParsedHeaderClause clause = new ParsedHeaderClause();
+ if (version != null) {
+ clause.attrs.put(Constants.VERSION_ATTRIBUTE, VersionRange.parseVersionRange(version.toString()));
+ }
+ clause.paths.add((String) export.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE));
+ clauseList.add(clause);
+ }
+ }
+
+ return clauseList;
+ }
+
+ private static List<Capability> calculateImplicitUses(
+ List<Capability> exports, List<ParsedHeaderClause> imports)
+ throws BundleException {
+ // Add a "uses" directive onto each export of R3 bundles
+ // that references every other import (which will include
+ // exports, since export implies import); this is
+ // necessary since R3 bundles assumed a single class space,
+ // but R4 allows for multiple class spaces.
+ String usesValue = "";
+ for (ParsedHeaderClause anImport : imports) {
+ for (int pathIdx = 0; pathIdx < anImport.paths.size(); pathIdx++) {
+ usesValue = usesValue
+ + ((usesValue.length() > 0) ? "," : "")
+ + anImport.paths.get(pathIdx);
+ }
+ }
+ for (int i = 0; i < exports.size(); i++) {
+ Map<String, String> dirs = new HashMap<String, String>(1);
+ dirs.put(Constants.USES_DIRECTIVE, usesValue);
+ exports.set(i, new CapabilityImpl(
+ exports.get(i).getResource(),
+ BundleRevision.PACKAGE_NAMESPACE,
+ dirs,
+ exports.get(i).getAttributes()));
+ }
+
+ return exports;
+ }
+
+ private static ParsedHeaderClause parseBundleSymbolicName(Map<String, String> headerMap)
+ throws BundleException {
+ List<ParsedHeaderClause> clauses = parseStandardHeader(headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+ if (clauses.size() > 0) {
+ if (clauses.size() > 1 || clauses.get(0).paths.size() > 1) {
+ throw new BundleException("Cannot have multiple symbolic names: " + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+ }
+
+ // Get bundle version.
+ Version bundleVersion = Version.emptyVersion;
+ if (headerMap.get(Constants.BUNDLE_VERSION) != null) {
+ bundleVersion = Version.parseVersion(headerMap.get(Constants.BUNDLE_VERSION));
+ }
+
+ // Create a require capability and return it.
+ ParsedHeaderClause clause = clauses.get(0);
+ String symName = clause.paths.get(0);
+ clause.attrs.put(BundleRevision.BUNDLE_NAMESPACE, symName);
+ clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersion);
+ return clause;
+ }
+
+ return null;
+ }
+
+ private static List<RequirementImpl> parseFragmentHost(
+ Resource resource, Map<String, String> headerMap)
+ throws BundleException {
+ List<RequirementImpl> reqs = new ArrayList<RequirementImpl>();
+
+ List<ParsedHeaderClause> clauses = parseStandardHeader(headerMap.get(Constants.FRAGMENT_HOST));
+ if (clauses.size() > 0) {
+ // Make sure that only one fragment host symbolic name is specified.
+ if (clauses.size() > 1 || clauses.get(0).paths.size() > 1) {
+ throw new BundleException("Fragments cannot have multiple hosts: " + headerMap.get(Constants.FRAGMENT_HOST));
+ }
+
+ // If the bundle-version attribute is specified, then convert
+ // it to the proper type.
+ Object value = clauses.get(0).attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+ value = (value == null) ? "0.0.0" : value;
+ clauses.get(0).attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, VersionRange.parseVersionRange(value.toString()));
+
+ // Note that we use a linked hash map here to ensure the
+ // host symbolic name is first, which will make indexing
+ // more efficient.
+ // TODO: OSGi R4.3 - This is ordering is kind of hacky.
+ // Prepend the host symbolic name to the map of attributes.
+ Map<String, Object> attrs = clauses.get(0).attrs;
+ Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1);
+ // We want this first from an indexing perspective.
+ newAttrs.put(BundleRevision.HOST_NAMESPACE, clauses.get(0).paths.get(0));
+ newAttrs.putAll(attrs);
+ // But we need to put it again to make sure it wasn't overwritten.
+ newAttrs.put(BundleRevision.HOST_NAMESPACE, clauses.get(0).paths.get(0));
+
+ // Create filter now so we can inject filter directive.
+ SimpleFilter sf = SimpleFilter.convert(newAttrs);
+
+ // Inject filter directive.
+ // TODO: OSGi R4.3 - Can we insert this on demand somehow?
+ Map<String, String> dirs = clauses.get(0).dirs;
+ Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1);
+ newDirs.putAll(dirs);
+ newDirs.put(Constants.FILTER_DIRECTIVE, sf.toString());
+
+ reqs.add(new RequirementImpl(
+ resource, BundleRevision.HOST_NAMESPACE,
+ newDirs,
+ newAttrs));
+ }
+
+ return reqs;
+ }
+
+ private static List<ParsedHeaderClause> normalizeRequireClauses(List<ParsedHeaderClause> clauses) {
+ // Convert bundle version attribute to VersionRange type.
+ for (ParsedHeaderClause clause : clauses) {
+ Object value = clause.attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
+ if (value != null) {
+ clause.attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, VersionRange.parseVersionRange(value.toString()));
+ }
+ }
+
+ return clauses;
+ }
+
+ private static List<Requirement> convertRequires(List<ParsedHeaderClause> clauses, Resource resource) {
+ List<Requirement> reqList = new ArrayList<Requirement>();
+ for (ParsedHeaderClause clause : clauses) {
+ for (String path : clause.paths) {
+ // Prepend the bundle symbolic name to the array of attributes.
+ Map<String, Object> attrs = clause.attrs;
+ // Note that we use a linked hash map here to ensure the
+ // symbolic name attribute is first, which will make indexing
+ // more efficient.
+ // TODO: OSGi R4.3 - This is ordering is kind of hacky.
+ // Prepend the symbolic name to the array of attributes.
+ Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1);
+ // We want this first from an indexing perspective.
+ newAttrs.put(BundleRevision.BUNDLE_NAMESPACE, path);
+ newAttrs.putAll(attrs);
+ // But we need to put it again to make sure it wasn't overwritten.
+ newAttrs.put(BundleRevision.BUNDLE_NAMESPACE, path);
+
+ // Create filter now so we can inject filter directive.
+ SimpleFilter sf = SimpleFilter.convert(newAttrs);
+
+ // Inject filter directive.
+ // TODO: OSGi R4.3 - Can we insert this on demand somehow?
+ Map<String, String> dirs = clause.dirs;
+ Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1);
+ newDirs.putAll(dirs);
+ newDirs.put(Constants.FILTER_DIRECTIVE, sf.toString());
+
+ // Create package requirement and add to requirement list.
+ reqList.add(new RequirementImpl(resource, BundleRevision.BUNDLE_NAMESPACE, newDirs, newAttrs));
+ }
+ }
+
+ return reqList;
+ }
+
+ private static final char EOF = (char) -1;
+
+ private static char charAt(int pos, String headers, int length)
+ {
+ if (pos >= length)
+ {
+ return EOF;
+ }
+ return headers.charAt(pos);
+ }
+
+ private static final int CLAUSE_START = 0;
+ private static final int PARAMETER_START = 1;
+ private static final int KEY = 2;
+ private static final int DIRECTIVE_OR_TYPEDATTRIBUTE = 4;
+ private static final int ARGUMENT = 8;
+ private static final int VALUE = 16;
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private static List<ParsedHeaderClause> parseStandardHeader(String header)
+ {
+ List<ParsedHeaderClause> clauses = new ArrayList<ParsedHeaderClause>();
+ if (header == null)
+ {
+ return clauses;
+ }
+ ParsedHeaderClause clause = null;
+ String key = null;
+ Map targetMap = null;
+ int state = CLAUSE_START;
+ int currentPosition = 0;
+ int startPosition = 0;
+ int length = header.length();
+ boolean quoted = false;
+ boolean escaped = false;
+
+ char currentChar = EOF;
+ do
+ {
+ currentChar = charAt(currentPosition, header, length);
+ switch (state)
+ {
+ case CLAUSE_START:
+ clause = new ParsedHeaderClause();
+ clauses.add(clause);
+ state = PARAMETER_START;
+ case PARAMETER_START:
+ startPosition = currentPosition;
+ state = KEY;
+ case KEY:
+ switch (currentChar)
+ {
+ case ':':
+ case '=':
+ key = header.substring(startPosition, currentPosition).trim();
+ startPosition = currentPosition + 1;
+ targetMap = clause.attrs;
+ state = currentChar == ':' ? DIRECTIVE_OR_TYPEDATTRIBUTE : ARGUMENT;
+ break;
+ case EOF:
+ case ',':
+ case ';':
+ clause.paths.add(header.substring(startPosition, currentPosition).trim());
+ state = currentChar == ',' ? CLAUSE_START : PARAMETER_START;
+ break;
+ default:
+ break;
+ }
+ currentPosition++;
+ break;
+ case DIRECTIVE_OR_TYPEDATTRIBUTE:
+ switch(currentChar)
+ {
+ case '=':
+ if (startPosition != currentPosition)
+ {
+ clause.types.put(key, header.substring(startPosition, currentPosition).trim());
+ }
+ else
+ {
+ targetMap = clause.dirs;
+ }
+ state = ARGUMENT;
+ startPosition = currentPosition + 1;
+ break;
+ default:
+ break;
+ }
+ currentPosition++;
+ break;
+ case ARGUMENT:
+ if (currentChar == '\"')
+ {
+ quoted = true;
+ currentPosition++;
+ }
+ else
+ {
+ quoted = false;
+ }
+ if (!Character.isWhitespace(currentChar)) {
+ state = VALUE;
+ }
+ else {
+ currentPosition++;
+ }
+ break;
+ case VALUE:
+ if (escaped)
+ {
+ escaped = false;
+ }
+ else
+ {
+ if (currentChar == '\\' )
+ {
+ escaped = true;
+ }
+ else if (quoted && currentChar == '\"')
+ {
+ quoted = false;
+ }
+ else if (!quoted)
+ {
+ String value = null;
+ switch(currentChar)
+ {
+ case EOF:
+ case ';':
+ case ',':
+ value = header.substring(startPosition, currentPosition).trim();
+ if (value.startsWith("\"") && value.endsWith("\""))
+ {
+ value = value.substring(1, value.length() - 1);
+ }
+ if (targetMap.put(key, value) != null)
+ {
+ throw new IllegalArgumentException(
+ "Duplicate '" + key + "' in: " + header);
+ }
+ state = currentChar == ';' ? PARAMETER_START : CLAUSE_START;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ currentPosition++;
+ break;
+ default:
+ break;
+ }
+ } while ( currentChar != EOF);
+
+ if (state > PARAMETER_START)
+ {
+ throw new IllegalArgumentException("Unable to parse header: " + header);
+ }
+ return clauses;
+ }
+
+ public static List<String> parseDelimitedString(String value, String delim)
+ {
+ return parseDelimitedString(value, delim, true);
+ }
+
+ /**
+ * Parses delimited string and returns an array containing the tokens. This
+ * parser obeys quotes, so the delimiter character will be ignored if it is
+ * inside of a quote. This method assumes that the quote character is not
+ * included in the set of delimiter characters.
+ * @param value the delimited string to parse.
+ * @param delim the characters delimiting the tokens.
+ * @return a list of string or an empty list if there are none.
+ **/
+ public static List<String> parseDelimitedString(String value, String delim, boolean trim)
+ {
+ if (value == null)
+ {
+ value = "";
+ }
+
+ List<String> list = new ArrayList();
+
+ int CHAR = 1;
+ int DELIMITER = 2;
+ int STARTQUOTE = 4;
+ int ENDQUOTE = 8;
+
+ StringBuffer sb = new StringBuffer();
+
+ int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+ boolean isEscaped = false;
+ for (int i = 0; i < value.length(); i++)
+ {
+ char c = value.charAt(i);
+
+ boolean isDelimiter = (delim.indexOf(c) >= 0);
+
+ if (!isEscaped && (c == '\\'))
+ {
+ isEscaped = true;
+ continue;
+ }
+
+ if (isEscaped)
+ {
+ sb.append(c);
+ }
+ else if (isDelimiter && ((expecting & DELIMITER) > 0))
+ {
+ if (trim)
+ {
+ list.add(sb.toString().trim());
+ }
+ else
+ {
+ list.add(sb.toString());
+ }
+ sb.delete(0, sb.length());
+ expecting = (CHAR | DELIMITER | STARTQUOTE);
+ }
+ else if ((c == '"') && ((expecting & STARTQUOTE) > 0))
+ {
+ sb.append(c);
+ expecting = CHAR | ENDQUOTE;
+ }
+ else if ((c == '"') && ((expecting & ENDQUOTE) > 0))
+ {
+ sb.append(c);
+ expecting = (CHAR | STARTQUOTE | DELIMITER);
+ }
+ else if ((expecting & CHAR) > 0)
+ {
+ sb.append(c);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid delimited string: " + value);
+ }
+
+ isEscaped = false;
+ }
+
+ if (sb.length() > 0)
+ {
+ if (trim)
+ {
+ list.add(sb.toString().trim());
+ }
+ else
+ {
+ list.add(sb.toString());
+ }
+ }
+
+ return list;
+ }
+
+
+ static class ParsedHeaderClause {
+ public final List<String> paths = new ArrayList<String>();
+ public final Map<String, String> dirs = new LinkedHashMap<String, String>();
+ public final Map<String, Object> attrs = new LinkedHashMap<String, Object>();
+ public final Map<String, String> types = new LinkedHashMap<String, String>();
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
new file mode 100644
index 0000000..18e0dc3
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.resolver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+/**
+ */
+public class ResourceImpl implements Resource {
+
+ private final List<Capability> m_caps;
+ private final List<Requirement> m_reqs;
+
+ public ResourceImpl(String name, Version version) {
+ this(name, IdentityNamespace.TYPE_BUNDLE, version);
+ }
+
+ public ResourceImpl(String name, String type, Version version)
+ {
+ m_caps = new ArrayList<Capability>();
+ m_caps.add(0, new IdentityCapability(this, name, type, version));
+ m_reqs = new ArrayList<Requirement>();
+ }
+
+ public void addCapability(Capability capability) {
+ assert capability.getResource() == this;
+ m_caps.add(capability);
+ }
+
+ public void addCapabilities(Iterable<? extends Capability> capabilities) {
+ for (Capability cap : capabilities) {
+ addCapability(cap);
+ }
+ }
+
+ public void addRequirement(Requirement requirement) {
+ assert requirement.getResource() == this;
+ m_reqs.add(requirement);
+ }
+
+ public void addRequirements(Iterable<? extends Requirement> requirements) {
+ for (Requirement req : requirements) {
+ addRequirement(req);
+ }
+ }
+
+ public List<Capability> getCapabilities(String namespace)
+ {
+ List<Capability> result = m_caps;
+ if (namespace != null)
+ {
+ result = new ArrayList<Capability>();
+ for (Capability cap : m_caps)
+ {
+ if (cap.getNamespace().equals(namespace))
+ {
+ result.add(cap);
+ }
+ }
+ }
+ return result;
+ }
+
+ public List<Requirement> getRequirements(String namespace)
+ {
+ List<Requirement> result = m_reqs;
+ if (namespace != null)
+ {
+ result = new ArrayList<Requirement>();
+ for (Requirement req : m_reqs)
+ {
+ if (req.getNamespace().equals(namespace))
+ {
+ result.add(req);
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ Capability cap = getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE).get(0);
+ return cap.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE) + "/"
+ + cap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ServiceNamespace.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ServiceNamespace.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ServiceNamespace.java
new file mode 100644
index 0000000..4fe3bf8
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ServiceNamespace.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.resolver;
+
+import org.osgi.resource.Namespace;
+
+/**
+ */
+public final class ServiceNamespace extends Namespace {
+
+ public static final String SERVICE_NAMESPACE = "service-reference";
+
+ private ServiceNamespace() {
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/SimpleFilter.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/SimpleFilter.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/SimpleFilter.java
new file mode 100644
index 0000000..ae10441
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/SimpleFilter.java
@@ -0,0 +1,649 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.resolver;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.felix.utils.version.VersionRange;
+
+public class SimpleFilter
+{
+ public static final int MATCH_ALL = 0;
+ public static final int AND = 1;
+ public static final int OR = 2;
+ public static final int NOT = 3;
+ public static final int EQ = 4;
+ public static final int LTE = 5;
+ public static final int GTE = 6;
+ public static final int SUBSTRING = 7;
+ public static final int PRESENT = 8;
+ public static final int APPROX = 9;
+
+ private final String m_name;
+ private final Object m_value;
+ private final int m_op;
+
+ public SimpleFilter(String attr, Object value, int op)
+ {
+ m_name = attr;
+ m_value = value;
+ m_op = op;
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public Object getValue()
+ {
+ return m_value;
+ }
+
+ public int getOperation()
+ {
+ return m_op;
+ }
+
+ public String toString()
+ {
+ String s = null;
+ switch (m_op)
+ {
+ case AND:
+ s = "(&" + toString((List) m_value) + ")";
+ break;
+ case OR:
+ s = "(|" + toString((List) m_value) + ")";
+ break;
+ case NOT:
+ s = "(!" + toString((List) m_value) + ")";
+ break;
+ case EQ:
+ s = "(" + m_name + "=" + toEncodedString(m_value) + ")";
+ break;
+ case LTE:
+ s = "(" + m_name + "<=" + toEncodedString(m_value) + ")";
+ break;
+ case GTE:
+ s = "(" + m_name + ">=" + toEncodedString(m_value) + ")";
+ break;
+ case SUBSTRING:
+ s = "(" + m_name + "=" + unparseSubstring((List<String>) m_value) + ")";
+ break;
+ case PRESENT:
+ s = "(" + m_name + "=*)";
+ break;
+ case APPROX:
+ s = "(" + m_name + "~=" + toEncodedString(m_value) + ")";
+ break;
+ case MATCH_ALL:
+ s = "(*)";
+ break;
+ }
+ return s;
+ }
+
+ private static String toString(List list)
+ {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < list.size(); i++)
+ {
+ sb.append(list.get(i).toString());
+ }
+ return sb.toString();
+ }
+
+ private static String toDecodedString(String s, int startIdx, int endIdx)
+ {
+ StringBuffer sb = new StringBuffer(endIdx - startIdx);
+ boolean escaped = false;
+ for (int i = 0; i < (endIdx - startIdx); i++)
+ {
+ char c = s.charAt(startIdx + i);
+ if (!escaped && (c == '\\'))
+ {
+ escaped = true;
+ }
+ else
+ {
+ escaped = false;
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private static String toEncodedString(Object o)
+ {
+ if (o instanceof String)
+ {
+ String s = (String) o;
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < s.length(); i++)
+ {
+ char c = s.charAt(i);
+ if ((c == '\\') || (c == '(') || (c == ')') || (c == '*'))
+ {
+ sb.append('\\');
+ }
+ sb.append(c);
+ }
+
+ o = sb.toString();
+ }
+
+ return o.toString();
+ }
+
+ public static SimpleFilter parse(String filter)
+ {
+ int idx = skipWhitespace(filter, 0);
+
+ if ((filter == null) || (filter.length() == 0) || (idx >= filter.length()))
+ {
+ throw new IllegalArgumentException("Null or empty filter.");
+ }
+ else if (filter.charAt(idx) != '(')
+ {
+ throw new IllegalArgumentException("Missing opening parenthesis: " + filter);
+ }
+
+ SimpleFilter sf = null;
+ List stack = new ArrayList();
+ boolean isEscaped = false;
+ while (idx < filter.length())
+ {
+ if (sf != null)
+ {
+ throw new IllegalArgumentException(
+ "Only one top-level operation allowed: " + filter);
+ }
+
+ if (!isEscaped && (filter.charAt(idx) == '('))
+ {
+ // Skip paren and following whitespace.
+ idx = skipWhitespace(filter, idx + 1);
+
+ if (filter.charAt(idx) == '&')
+ {
+ int peek = skipWhitespace(filter, idx + 1);
+ if (filter.charAt(peek) == '(')
+ {
+ idx = peek - 1;
+ stack.add(0, new SimpleFilter(null, new ArrayList(), SimpleFilter.AND));
+ }
+ else
+ {
+ stack.add(0, new Integer(idx));
+ }
+ }
+ else if (filter.charAt(idx) == '|')
+ {
+ int peek = skipWhitespace(filter, idx + 1);
+ if (filter.charAt(peek) == '(')
+ {
+ idx = peek - 1;
+ stack.add(0, new SimpleFilter(null, new ArrayList(), SimpleFilter.OR));
+ }
+ else
+ {
+ stack.add(0, new Integer(idx));
+ }
+ }
+ else if (filter.charAt(idx) == '!')
+ {
+ int peek = skipWhitespace(filter, idx + 1);
+ if (filter.charAt(peek) == '(')
+ {
+ idx = peek - 1;
+ stack.add(0, new SimpleFilter(null, new ArrayList(), SimpleFilter.NOT));
+ }
+ else
+ {
+ stack.add(0, new Integer(idx));
+ }
+ }
+ else
+ {
+ stack.add(0, new Integer(idx));
+ }
+ }
+ else if (!isEscaped && (filter.charAt(idx) == ')'))
+ {
+ Object top = stack.remove(0);
+ if (top instanceof SimpleFilter)
+ {
+ if (!stack.isEmpty() && (stack.get(0) instanceof SimpleFilter))
+ {
+ ((List) ((SimpleFilter) stack.get(0)).m_value).add(top);
+ }
+ else
+ {
+ sf = (SimpleFilter) top;
+ }
+ }
+ else if (!stack.isEmpty() && (stack.get(0) instanceof SimpleFilter))
+ {
+ ((List) ((SimpleFilter) stack.get(0)).m_value).add(
+ SimpleFilter.subfilter(filter, ((Integer) top).intValue(), idx));
+ }
+ else
+ {
+ sf = SimpleFilter.subfilter(filter, ((Integer) top).intValue(), idx);
+ }
+ }
+ else if (!isEscaped && (filter.charAt(idx) == '\\'))
+ {
+ isEscaped = true;
+ }
+ else
+ {
+ isEscaped = false;
+ }
+
+ idx = skipWhitespace(filter, idx + 1);
+ }
+
+ if (sf == null)
+ {
+ throw new IllegalArgumentException("Missing closing parenthesis: " + filter);
+ }
+
+ return sf;
+ }
+
+ private static SimpleFilter subfilter(String filter, int startIdx, int endIdx)
+ {
+ final String opChars = "=<>~";
+
+ // Determine the ending index of the attribute name.
+ int attrEndIdx = startIdx;
+ for (int i = 0; i < (endIdx - startIdx); i++)
+ {
+ char c = filter.charAt(startIdx + i);
+ if (opChars.indexOf(c) >= 0)
+ {
+ break;
+ }
+ else if (!Character.isWhitespace(c))
+ {
+ attrEndIdx = startIdx + i + 1;
+ }
+ }
+ if (attrEndIdx == startIdx)
+ {
+ throw new IllegalArgumentException(
+ "Missing attribute name: " + filter.substring(startIdx, endIdx));
+ }
+ String attr = filter.substring(startIdx, attrEndIdx);
+
+ // Skip the attribute name and any following whitespace.
+ startIdx = skipWhitespace(filter, attrEndIdx);
+
+ // Determine the operator type.
+ int op = -1;
+ switch (filter.charAt(startIdx))
+ {
+ case '=':
+ op = EQ;
+ startIdx++;
+ break;
+ case '<':
+ if (filter.charAt(startIdx + 1) != '=')
+ {
+ throw new IllegalArgumentException(
+ "Unknown operator: " + filter.substring(startIdx, endIdx));
+ }
+ op = LTE;
+ startIdx += 2;
+ break;
+ case '>':
+ if (filter.charAt(startIdx + 1) != '=')
+ {
+ throw new IllegalArgumentException(
+ "Unknown operator: " + filter.substring(startIdx, endIdx));
+ }
+ op = GTE;
+ startIdx += 2;
+ break;
+ case '~':
+ if (filter.charAt(startIdx + 1) != '=')
+ {
+ throw new IllegalArgumentException(
+ "Unknown operator: " + filter.substring(startIdx, endIdx));
+ }
+ op = APPROX;
+ startIdx += 2;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown operator: " + filter.substring(startIdx, endIdx));
+ }
+
+ // Parse value.
+ Object value = toDecodedString(filter, startIdx, endIdx);
+
+ // Check if the equality comparison is actually a substring
+ // or present operation.
+ if (op == EQ)
+ {
+ String valueStr = filter.substring(startIdx, endIdx);
+ List<String> values = parseSubstring(valueStr);
+ if ((values.size() == 2)
+ && (values.get(0).length() == 0)
+ && (values.get(1).length() == 0))
+ {
+ op = PRESENT;
+ }
+ else if (values.size() > 1)
+ {
+ op = SUBSTRING;
+ value = values;
+ }
+ }
+
+ return new SimpleFilter(attr, value, op);
+ }
+
+ public static List<String> parseSubstring(String value)
+ {
+ List<String> pieces = new ArrayList();
+ StringBuffer ss = new StringBuffer();
+ // int kind = SIMPLE; // assume until proven otherwise
+ boolean wasStar = false; // indicates last piece was a star
+ boolean leftstar = false; // track if the initial piece is a star
+ boolean rightstar = false; // track if the final piece is a star
+
+ int idx = 0;
+
+ // We assume (sub)strings can contain leading and trailing blanks
+ boolean escaped = false;
+ loop: for (;;)
+ {
+ if (idx >= value.length())
+ {
+ if (wasStar)
+ {
+ // insert last piece as "" to handle trailing star
+ rightstar = true;
+ }
+ else
+ {
+ pieces.add(ss.toString());
+ // accumulate the last piece
+ // note that in the case of
+ // (cn=); this might be
+ // the string "" (!=null)
+ }
+ ss.setLength(0);
+ break loop;
+ }
+
+ // Read the next character and account for escapes.
+ char c = value.charAt(idx++);
+ if (!escaped && (c == '*'))
+ {
+ // If we have successive '*' characters, then we can
+ // effectively collapse them by ignoring succeeding ones.
+ if (!wasStar)
+ {
+ if (ss.length() > 0)
+ {
+ pieces.add(ss.toString()); // accumulate the pieces
+ // between '*' occurrences
+ }
+ ss.setLength(0);
+ // if this is a leading star, then track it
+ if (pieces.isEmpty())
+ {
+ leftstar = true;
+ }
+ wasStar = true;
+ }
+ }
+ else if (!escaped && (c == '\\'))
+ {
+ escaped = true;
+ }
+ else
+ {
+ escaped = false;
+ wasStar = false;
+ ss.append(c);
+ }
+ }
+ if (leftstar || rightstar || pieces.size() > 1)
+ {
+ // insert leading and/or trailing "" to anchor ends
+ if (rightstar)
+ {
+ pieces.add("");
+ }
+ if (leftstar)
+ {
+ pieces.add(0, "");
+ }
+ }
+ return pieces;
+ }
+
+ public static String unparseSubstring(List<String> pieces)
+ {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < pieces.size(); i++)
+ {
+ if (i > 0)
+ {
+ sb.append("*");
+ }
+ sb.append(toEncodedString(pieces.get(i)));
+ }
+ return sb.toString();
+ }
+
+ public static boolean compareSubstring(List<String> pieces, String s)
+ {
+ // Walk the pieces to match the string
+ // There are implicit stars between each piece,
+ // and the first and last pieces might be "" to anchor the match.
+ // assert (pieces.length > 1)
+ // minimal case is <string>*<string>
+
+ boolean result = true;
+ int len = pieces.size();
+
+ // Special case, if there is only one piece, then
+ // we must perform an equality test.
+ if (len == 1)
+ {
+ return s.equals(pieces.get(0));
+ }
+
+ // Otherwise, check whether the pieces match
+ // the specified string.
+
+ int index = 0;
+
+ loop: for (int i = 0; i < len; i++)
+ {
+ String piece = pieces.get(i);
+
+ // If this is the first piece, then make sure the
+ // string starts with it.
+ if (i == 0)
+ {
+ if (!s.startsWith(piece))
+ {
+ result = false;
+ break loop;
+ }
+ }
+
+ // If this is the last piece, then make sure the
+ // string ends with it.
+ if (i == (len - 1))
+ {
+ if (s.endsWith(piece) && (s.length() >= (index + piece.length())))
+ {
+ result = true;
+ }
+ else
+ {
+ result = false;
+ }
+ break loop;
+ }
+
+ // If this is neither the first or last piece, then
+ // make sure the string contains it.
+ if ((i > 0) && (i < (len - 1)))
+ {
+ index = s.indexOf(piece, index);
+ if (index < 0)
+ {
+ result = false;
+ break loop;
+ }
+ }
+
+ // Move string index beyond the matching piece.
+ index += piece.length();
+ }
+
+ return result;
+ }
+
+ private static int skipWhitespace(String s, int startIdx)
+ {
+ int len = s.length();
+ while ((startIdx < len) && Character.isWhitespace(s.charAt(startIdx)))
+ {
+ startIdx++;
+ }
+ return startIdx;
+ }
+
+ /**
+ * Converts a attribute map to a filter. The filter is created by iterating
+ * over the map's entry set. If ordering of attributes is important (e.g.,
+ * for hitting attribute indices), then the map's entry set should iterate
+ * in the desired order. Equality testing is assumed for all attribute types
+ * other than version ranges, which are handled appropriated. If the attribute
+ * map is empty, then a filter that matches anything is returned.
+ * @param attrs Map of attributes to convert to a filter.
+ * @return A filter corresponding to the attributes.
+ */
+ public static SimpleFilter convert(Map<String, Object> attrs)
+ {
+ // Rather than building a filter string to be parsed into a SimpleFilter,
+ // we will just create the parsed SimpleFilter directly.
+
+ List<SimpleFilter> filters = new ArrayList<SimpleFilter>();
+
+ for (Entry<String, Object> entry : attrs.entrySet())
+ {
+ if (entry.getValue() instanceof VersionRange)
+ {
+ VersionRange vr = (VersionRange) entry.getValue();
+ if (!vr.isOpenFloor())
+ {
+ filters.add(
+ new SimpleFilter(
+ entry.getKey(),
+ vr.getFloor().toString(),
+ SimpleFilter.GTE));
+ }
+ else
+ {
+ SimpleFilter not =
+ new SimpleFilter(null, new ArrayList(), SimpleFilter.NOT);
+ ((List) not.getValue()).add(
+ new SimpleFilter(
+ entry.getKey(),
+ vr.getFloor().toString(),
+ SimpleFilter.LTE));
+ filters.add(not);
+ }
+
+ if (vr.getCeiling() != null)
+ {
+ if (!vr.isOpenCeiling())
+ {
+ filters.add(
+ new SimpleFilter(
+ entry.getKey(),
+ vr.getCeiling().toString(),
+ SimpleFilter.LTE));
+ }
+ else
+ {
+ SimpleFilter not =
+ new SimpleFilter(null, new ArrayList(), SimpleFilter.NOT);
+ ((List) not.getValue()).add(
+ new SimpleFilter(
+ entry.getKey(),
+ vr.getCeiling().toString(),
+ SimpleFilter.GTE));
+ filters.add(not);
+ }
+ }
+ }
+ else
+ {
+ List<String> values = SimpleFilter.parseSubstring(entry.getValue().toString());
+ if (values.size() > 1)
+ {
+ filters.add(
+ new SimpleFilter(
+ entry.getKey(),
+ values,
+ SimpleFilter.SUBSTRING));
+ }
+ else
+ {
+ filters.add(
+ new SimpleFilter(
+ entry.getKey(),
+ values.get(0),
+ SimpleFilter.EQ));
+ }
+ }
+ }
+
+ SimpleFilter sf = null;
+
+ if (filters.size() == 1)
+ {
+ sf = filters.get(0);
+ }
+ else if (attrs.size() > 1)
+ {
+ sf = new SimpleFilter(null, filters, SimpleFilter.AND);
+ }
+ else if (filters.isEmpty())
+ {
+ sf = new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+ }
+
+ return sf;
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/Slf4jResolverLog.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/Slf4jResolverLog.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/Slf4jResolverLog.java
new file mode 100644
index 0000000..2f4a1f3
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/Slf4jResolverLog.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.resolver;
+
+import org.slf4j.Logger;
+
+/**
+ */
+public class Slf4jResolverLog extends org.apache.felix.resolver.Logger {
+
+ private final Logger logger;
+
+ public Slf4jResolverLog(Logger logger) {
+ super(LOG_DEBUG);
+ this.logger = logger;
+ }
+
+ @Override
+ protected void doLog(int level, String msg, Throwable throwable) {
+ switch (level) {
+ case LOG_ERROR:
+ logger.error(msg, throwable);
+ break;
+ case LOG_WARNING:
+ logger.warn(msg, throwable);
+ break;
+ case LOG_INFO:
+ logger.info(msg, throwable);
+ break;
+ case LOG_DEBUG:
+ logger.debug(msg, throwable);
+ break;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/resolver/UriNamespace.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/UriNamespace.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/UriNamespace.java
new file mode 100644
index 0000000..b5158bf
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/UriNamespace.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.resolver;
+
+import java.util.List;
+
+import org.osgi.resource.Capability;
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Resource;
+
+/**
+ */
+public final class UriNamespace extends Namespace {
+
+ public static final String URI_NAMESPACE = "karaf.uri";
+
+ public static String getUri(Resource resource)
+ {
+ List<Capability> caps = resource.getCapabilities(null);
+ for (Capability cap : caps)
+ {
+ if (cap.getNamespace().equals(UriNamespace.URI_NAMESPACE))
+ {
+ return cap.getAttributes().get(UriNamespace.URI_NAMESPACE).toString();
+ }
+ }
+ return null;
+ }
+
+
+ private UriNamespace() {
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/0c8e8a81/features/core/src/main/java/org/apache/karaf/features/internal/service/Artifact.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Artifact.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Artifact.java
new file mode 100644
index 0000000..44e9a7c
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Artifact.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.service;
+
+import java.net.URI;
+
+/**
+ * Simple abstraction of a maven artifact to avoid external deps
+ */
+public class Artifact {
+ String groupId;
+ String artifactId;
+ String version;
+ String extension;
+ String classifier;
+
+ public Artifact(String coords) {
+ String[] coordsAr = coords.split(":");
+ if (coordsAr.length != 5) {
+ throw new IllegalArgumentException("Maven URL " + coords + " is malformed or not complete");
+ }
+ this.groupId = coordsAr[0];
+ this.artifactId = coordsAr[1];
+ this.version = coordsAr[4];
+ this.extension = coordsAr[2];
+ this.classifier = coordsAr[3];
+ }
+
+ public Artifact(String coords, String version) {
+ this(coords);
+ this.version = version;
+ }
+
+ public URI getMavenUrl(String version) {
+ String uriSt = "mvn:" + this.groupId + "/" + this.artifactId + "/" + version + "/" + this.extension + "/" + this.classifier;
+ try {
+ return new URI(uriSt);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}