You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by zo...@apache.org on 2011/02/27 21:02:56 UTC

svn commit: r1075127 [6/7] - in /aries/tags/application-0.2.1: ./ application-api/ application-api/src/ application-api/src/main/ application-api/src/main/java/ application-api/src/main/java/org/ application-api/src/main/java/org/apache/ application-ap...

Added: aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/management/SimpleBundleInfo.java
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/management/SimpleBundleInfo.java?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/management/SimpleBundleInfo.java (added)
+++ aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/management/SimpleBundleInfo.java Sun Feb 27 20:02:48 2011
@@ -0,0 +1,164 @@
+/*
+ * 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 WARRANTIESOR 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.aries.application.utils.management;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+import java.util.jar.Attributes;
+
+import org.apache.aries.application.ApplicationMetadataFactory;
+import org.apache.aries.application.Content;
+import org.apache.aries.application.management.BundleInfo;
+import org.apache.aries.application.utils.manifest.BundleManifest;
+import org.apache.aries.application.utils.manifest.ManifestHeaderProcessor;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+public final class SimpleBundleInfo implements BundleInfo {
+  private Content _symbolicName;
+  private Version _version;
+  private Attributes _attributes;
+  private Set<Content> _exportPackages = null;
+  private Set<Content> _importPackages = null;
+  private Set<Content> _exportServices = null;
+  private Set<Content> _importServices = null;
+  private Set<Content> _requireBundle = null;
+  
+  private String _location;
+  private ApplicationMetadataFactory _applicationMetadataFactory;
+  
+  public SimpleBundleInfo(ApplicationMetadataFactory amf, BundleManifest bm, String location) { 
+    _symbolicName = amf.parseContent(bm.getSymbolicName());
+    _version = bm.getVersion();
+    _attributes = bm.getRawAttributes();
+    _location = location;
+    _applicationMetadataFactory = amf;
+  }
+  
+  public Set<Content> getExportPackage() {
+    if (_exportPackages == null) { 
+      _exportPackages = getContentSetFromHeader (_attributes, Constants.EXPORT_PACKAGE);
+    }
+    return _exportPackages;
+  }
+  
+  public Set<Content> getExportService() {
+    if (_exportServices == null) {
+      _exportServices = getContentSetFromHeader (_attributes, Constants.EXPORT_SERVICE);
+    }
+    return _exportPackages;
+  }
+
+  public Map<String, String> getHeaders() {
+    Map<String, String> result = new HashMap<String, String>();
+    for (Entry<Object, Object> h: _attributes.entrySet()) {
+      Attributes.Name name = (Attributes.Name) h.getKey();
+      String value = (String) h.getValue();
+      result.put(name.toString(), value);
+    }
+    return result;
+  }
+
+  public Set<Content> getImportPackage() {
+    if (_importPackages == null) { 
+      _importPackages = getContentSetFromHeader (_attributes, Constants.IMPORT_PACKAGE);
+    }
+    return _importPackages;
+  }
+
+  public Set<Content> getImportService() {
+    if (_importServices == null) {
+      _importServices = getContentSetFromHeader (_attributes, Constants.IMPORT_SERVICE);
+    }
+    return _importServices;
+  }
+
+  public String getLocation() {
+    return _location;
+  }
+
+  public String getSymbolicName() {
+    return _symbolicName.getContentName();
+  }
+
+  public Version getVersion() {
+    return _version;
+  }
+
+  private Set<Content> getContentSetFromHeader (Attributes attributes, String key) {
+    String header = _attributes.getValue(key);
+    List<String> splitHeader = ManifestHeaderProcessor.split(header, ",");
+    HashSet<Content> result = new HashSet<Content>();
+    for (String s: splitHeader) { 
+      Content c = _applicationMetadataFactory.parseContent(s);
+      result.add(c);
+    }
+    return result;
+  }
+
+  public Map<String, String> getBundleAttributes()
+  {
+    return _symbolicName.getAttributes();
+  }
+
+  public Map<String, String> getBundleDirectives()
+  {
+    return _symbolicName.getDirectives();
+  }
+
+  public Set<Content> getRequireBundle()
+  {
+    if (_requireBundle == null) {
+      _requireBundle = getContentSetFromHeader(_attributes, Constants.REQUIRE_BUNDLE);
+    }
+    
+    return _requireBundle;
+  }
+  
+  /**
+   * Equality is just based on the location. If you install a bundle from the same location string
+   * you get the same Bundle, even if the underlying bundle had a different symbolic name/version.
+   * This seems reasonable and quick.
+   */
+  public boolean equals(Object other)
+  {
+    if (other == null) return false;
+    if (other == this) return true;
+    if (other instanceof SimpleBundleInfo) {
+      return _location.equals(((SimpleBundleInfo)other)._location);
+    }
+    
+    return false;
+  }
+  
+  public int hashCode()
+  {
+    return _location.hashCode();
+  }
+  
+  public String toString()
+  {
+    return _symbolicName.getContentName() + "_" + getVersion();
+  }
+}
\ No newline at end of file

Added: aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/BundleManifest.java
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/BundleManifest.java?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/BundleManifest.java (added)
+++ aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/BundleManifest.java Sun Feb 27 20:02:48 2011
@@ -0,0 +1,198 @@
+/*
+ * 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 WARRANTIESOR 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.aries.application.utils.manifest;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.apache.aries.application.filesystem.IFile;
+import org.apache.aries.application.utils.filesystem.IOUtils;
+import org.apache.aries.application.utils.internal.MessageUtil;
+import org.apache.aries.application.utils.manifest.ManifestHeaderProcessor.NameValueMap;
+import org.apache.aries.application.utils.manifest.ManifestHeaderProcessor.NameValuePair;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Entity class to retrieve and represent a bundle manifest (valid or invalid).
+ */
+public class BundleManifest
+{
+  private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
+  private static final Logger _logger = LoggerFactory.getLogger("org.apache.aries.application.utils");
+
+  /**
+   * Read a manifest from a jar input stream. This will find the manifest even if it is NOT
+   * the first file in the archive.
+   * 
+   * @param is
+   * @return
+   */
+  public static BundleManifest fromBundle(InputStream is) {
+    JarInputStream jarIs = null;
+    try {
+      jarIs = new JarInputStream(is);
+      Manifest m = jarIs.getManifest();
+      if (m != null)
+        return new BundleManifest(m);
+      else {
+        ZipEntry entry;
+        while ((entry = jarIs.getNextEntry()) != null) {
+          if (entry.getName().equals(MANIFEST_PATH))
+            return new BundleManifest(jarIs);
+        }
+        
+        return null;
+      }
+    }
+    catch (IOException e) {
+      _logger.error ("IOException in BundleManifest()", e);
+      return null;
+    }
+    finally {
+      IOUtils.close(jarIs);
+    }
+  }
+  
+  /**
+   * Retrieve a BundleManifest from the given jar file
+   * 
+   * @param f
+   * @return
+   */
+  public static BundleManifest fromBundle(IFile f) {
+    InputStream is = null;
+    try {
+      if (f.isDirectory()) {
+        IFile manFile = f.convert().getFile(MANIFEST_PATH);
+        if (manFile != null)
+          return new BundleManifest(manFile.open());
+        else
+          return null;
+      } else {
+        is = f.open();
+        return fromBundle(is);
+      }
+    } catch (IOException e) {
+      _logger.error ("IOException in BundleManifest.fromBundle(IFile)", e);
+      return null;
+    }
+    finally {
+      IOUtils.close(is);
+    }
+  }
+  
+  /**
+   * Retrieve a bundle manifest from the given jar file, which can be exploded or compressed
+   * 
+   * @param f
+   * @return
+   */
+  public static BundleManifest fromBundle(File f) {
+    if (f.isDirectory()) {
+      File manifestFile = new File(f, MANIFEST_PATH);
+      if (manifestFile.isFile())
+        try {
+          return new BundleManifest(new FileInputStream(manifestFile));
+        }
+        catch (IOException e) {
+          _logger.error ("IOException in BundleManifest.fromBundle(File)", e);
+          return null;
+        }
+      else
+        return null;
+    }
+    else  if (f.isFile()) {
+      try {
+        return fromBundle(new FileInputStream(f));
+      }
+      catch (IOException e) {
+        _logger.error ("IOException in BundleManifest.fromBundle(File)", e);
+        return null;
+      }
+    }
+    else {
+      throw new IllegalArgumentException(MessageUtil.getMessage("APPUTILS0007E", f.getAbsolutePath()));
+    }
+  }
+  
+  private Manifest manifest;
+  
+  /**
+   * Create a BundleManifest object from the InputStream to the manifest (not to the bundle)
+   * @param manifestIs
+   * @throws IOException
+   */
+  public BundleManifest(InputStream manifestIs) throws IOException {
+    this(ManifestProcessor.parseManifest(manifestIs));
+  }
+  
+  /**
+   * Create a BundleManifest object from a common Manifest object
+   * @param m
+   */
+  public BundleManifest(Manifest m) {
+    manifest = m;
+  }
+  
+  public String getSymbolicName() {
+    String rawSymName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+
+    String result = null;
+    if (rawSymName != null) {
+      NameValuePair<String, NameValueMap<String, String>> info = ManifestHeaderProcessor.parseBundleSymbolicName(rawSymName);
+      result = info.getName();
+    }
+    
+    return result;
+  }
+  
+  public Version getVersion() {
+    String specifiedVersion = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+    Version result = (specifiedVersion == null) ? Version.emptyVersion : new Version(specifiedVersion);
+    
+    return result;
+  }
+  
+  public String getManifestVersion() {
+    return manifest.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION);
+  }
+  
+  public Attributes getRawAttributes() {
+    return manifest.getMainAttributes();
+  }
+  
+  public Manifest getRawManifest() {
+    return manifest;
+  }
+  
+  public boolean isValid() {
+    return getManifestVersion() != null && getSymbolicName() != null;
+  }
+}
+

Added: aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestDefaultsInjector.java
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestDefaultsInjector.java?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestDefaultsInjector.java (added)
+++ aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestDefaultsInjector.java Sun Feb 27 20:02:48 2011
@@ -0,0 +1,281 @@
+/*
+ * 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 WARRANTIESOR 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.aries.application.utils.manifest;
+
+import java.io.File;
+import java.util.Map;
+import java.util.jar.Manifest;
+
+import org.apache.aries.application.filesystem.IDirectory;
+import org.apache.aries.application.filesystem.IFile;
+import org.apache.aries.application.utils.AppConstants;
+import org.apache.aries.application.utils.filesystem.FileSystem;
+import org.osgi.framework.Version;
+
+public class ManifestDefaultsInjector
+{
+  /**
+   * Quick adapter to update a Manifest, using content of a Zip File.
+   * <p>
+   * This is really a wrapper of updateManifest(Manifest,String,IDirectory), with the
+   * IDirectory being being created from the Zip File. This method avoids other Bundles
+   * requiring IDirectory solely for calling updateManifest.
+   * <p>
+   * @param mf Manifest to be updated
+   * @param appName The name to use for this app, if the name contains 
+   * a '_' char then the portion after the '_' is used, if possible, as 
+   * the application version.
+   * @param zip Content to use for application.
+   * @return true if manifest modified, false otherwise.
+   */
+  public static boolean updateManifest(Manifest mf, String appName, File zip){
+    IDirectory appPathIDir = FileSystem.getFSRoot(zip);
+    boolean result = updateManifest(mf, appName, appPathIDir);
+    return result;
+  }
+  
+  /**
+   * Tests the supplied manifest for the presence of expected 
+   * attributes, and where missing, adds them, and defaults 
+   * their values appropriately.
+   * 
+   * @param mf The manifest to test & update if needed.
+   * @param appName The name to use for this app, if the name contains 
+   * a '_' char then the portion after the '_' is used, if possible, as 
+   * the application version.
+   * @param appDir The IDirectory to scan to build application content
+   * property
+   * @return true if manifest modified, false otherwise.
+   */
+  public static boolean updateManifest(Manifest mf, String appName, IDirectory appDir){ 
+    Map<String, String> props = ManifestProcessor.readManifestIntoMap(mf);
+    String extracted[] = extractAppNameAndVersionFromNameIfPossible(appName);
+    String name = extracted[0];
+    String version = extracted[1];
+
+    boolean updated = false;
+    updated |= defaultAppSymbolicName(mf, props, name);
+    updated |= defaultAppName(mf, props, name);
+    updated |= defaultVersion(mf, props, version);
+    updated |= defaultAppScope(mf, props, name, version);
+    updated |= defaultAppContent(mf, props, appDir);
+    
+    return updated;
+  }
+  
+  /**
+   * Takes a compound name_version string, and returns the Name & Version information. 
+   * <p>
+   * @param name Contains name data related to this app. Expected format is   name_version  
+   * @return Array of String, index 0 is appName, index 1 is Version. 
+   * <br> Name will be the appname retrieved from the 'name' argument, Version will be the 
+   * version if found and valid, otherwise will be defaulted.
+   */
+  private static String[] extractAppNameAndVersionFromNameIfPossible(String name){
+    String retval[] = new String[2];
+    String appName = name;
+    String defaultedVersion;
+
+    int index = name.indexOf('_');
+
+    if (index != -1) {
+      appName = name.substring(0, index);
+      defaultedVersion = name.substring(index + 1);
+
+      try {
+        new Version(defaultedVersion);
+      } catch (IllegalArgumentException e) {
+        // this is not an error condition
+        defaultedVersion = AppConstants.DEFAULT_VERSION;
+      }
+    } else {
+      defaultedVersion = AppConstants.DEFAULT_VERSION;
+    }
+
+    retval[0] = appName;
+    retval[1] = defaultedVersion;
+    
+    return retval;  
+  }
+  
+  /**
+   * Sets the app symbolic name into the manifest, if not already present.
+   * 
+   * @param mf manifest to update
+   * @param props parsed manifest used to test if already present. 
+   * @param appName used for name if missing
+   * @return true if manifest is modified, false otherwise.
+   */
+  private static boolean defaultAppSymbolicName(Manifest mf, Map<String, String> props, String appName){
+    boolean updated = false;
+    if (!props.containsKey(AppConstants.APPLICATION_SYMBOLIC_NAME)) {
+      mf.getMainAttributes().putValue(AppConstants.APPLICATION_SYMBOLIC_NAME, appName);
+      updated = true;
+    }
+    return updated;    
+  }
+  
+  /**
+   * Sets the app name into the manifest, if not already present.
+   * 
+   * @param mf manifest to update
+   * @param props parsed manifest used to test if already present. 
+   * @param appName used for name if missing
+   * @return true if manifest is modified, false otherwise.
+   */  
+  private static boolean defaultAppName(Manifest mf, Map<String, String> props, String appName){
+    boolean updated = false;
+    if (!props.containsKey(AppConstants.APPLICATION_NAME)) {
+      mf.getMainAttributes().putValue(AppConstants.APPLICATION_NAME, appName);
+      updated = true;
+    }
+    return updated;    
+  }
+    
+  /**
+   * Sets the app version into the manifest, if not already present.
+   * 
+   * @param mf manifest to update
+   * @param props parsed manifest used to test if already present. 
+   * @param appVersion used for version if missing
+   * @return true if manifest is modified, false otherwise.
+   */  
+  private static boolean defaultVersion(Manifest mf, Map<String, String> props, String appVersion){
+    boolean updated = false;
+    if (!props.containsKey(AppConstants.APPLICATION_VERSION)) {
+      mf.getMainAttributes().putValue(AppConstants.APPLICATION_VERSION, appVersion);
+      updated = true;
+    }
+    return updated;
+  }
+  
+  /**
+   * Sets the app scope into the manifest, if not already present.
+   * 
+   * @param mf manifest to update
+   * @param props parsed manifest used to test if already present. 
+   * @param name used to build appScope if app symbolic name not set.
+   * @param version used to build appScope if app version missing.
+   * @return true if manifest is modified, false otherwise.
+   */   
+  private static boolean defaultAppScope(Manifest mf, Map<String, String> props, String name, String version){
+    boolean updated = false;
+    if (!props.containsKey(AppConstants.APPLICATION_SCOPE)) {
+
+      String appSymbolicName;
+      if (props.containsKey(AppConstants.APPLICATION_SYMBOLIC_NAME)) {
+        appSymbolicName = props.get(AppConstants.APPLICATION_SYMBOLIC_NAME);
+      } else {
+        appSymbolicName = name;
+      }
+
+      String appVersion;
+      if (props.containsKey(AppConstants.APPLICATION_VERSION)) {
+        appVersion = props.get(AppConstants.APPLICATION_VERSION);
+      } else {
+        appVersion = version;
+      }
+
+      String appScope = appSymbolicName + '_' + appVersion;
+      mf.getMainAttributes().putValue(AppConstants.APPLICATION_SCOPE, appScope);
+      updated = true;
+    }
+    return updated;
+  }
+  
+  /**
+   * Sets the app content into the manifest, if not already present.
+   * <p>
+   * This method will NOT set the appcontent if it is unable to build it.
+   * This is important, as the absence of appcontent is used by some callers
+   * to test if a manifest contains all required content.
+   * 
+   * @param mf manifest to update
+   * @param props parsed manifest used to test if already present. 
+   * @param appDir used to build app content if missing.
+   * @return true if manifest is modified, false otherwise.
+   */    
+  private static boolean defaultAppContent(Manifest mf, Map<String, String> props, IDirectory appDir){
+    boolean updated = false;
+    if (!props.containsKey(AppConstants.APPLICATION_CONTENT)) {
+      String appContent = calculateAppContent(appDir);
+      if (appContent != null) {
+        mf.getMainAttributes().putValue(AppConstants.APPLICATION_CONTENT, appContent);
+        updated = true;
+      }
+    }
+    return updated;    
+  }
+  
+  /**
+   * Processes an IDirectory to find targets that require adding to the application content attrib.
+   * 
+   * @param appDir The IDirectory to scan
+   * @return AppContent string, or null if no content was found.
+   */
+  private static String calculateAppContent(IDirectory appDir){
+    StringBuilder builder = new StringBuilder();
+    for (IFile file : appDir) {
+      processPossibleBundle(file, builder);
+    }
+    String returnVal = null;
+    if (builder.length() > 0) {
+      builder.deleteCharAt(builder.length() - 1);
+      returnVal = builder.toString();
+    }
+    return returnVal;
+  }
+  
+  /**
+   * This method works out if the given IFile represents an OSGi bundle and if
+   * it is then we append a matching rule to the String builder.
+   * 
+   * @param file    to file to check.
+   * @param builder the builder to append to.
+   */
+  private static void processPossibleBundle(IFile file, StringBuilder builder)
+  {
+    if (file.isDirectory() || (file.isFile() && (file.getName().endsWith(".jar") || file.getName().endsWith(".war")))) {
+      BundleManifest bundleMf = BundleManifest.fromBundle(file);
+      if (bundleMf != null) {
+        String manifestVersion = bundleMf.getManifestVersion();
+        String name = bundleMf.getSymbolicName();
+        String version = bundleMf.getVersion().toString();
+
+        // if the bundle manifest version is 2 AND a symbolic name is specified we have a valid bundle
+        if ("2".equals(manifestVersion) && name != null) {
+
+          builder.append(name);
+
+          // bundle version is not a required manifest header
+          if (version != null) {
+            builder.append(";version=\"[");
+            builder.append(version);
+            builder.append(',');
+            builder.append(version);
+            builder.append("]\"");
+          }
+
+          // the last comma will be removed once all content has been added
+          builder.append(",");
+        }
+      }
+    }
+  }
+}

Added: aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestHeaderProcessor.java
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestHeaderProcessor.java?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestHeaderProcessor.java (added)
+++ aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestHeaderProcessor.java Sun Feb 27 20:02:48 2011
@@ -0,0 +1,700 @@
+/*
+ * 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 WARRANTIESOR 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.aries.application.utils.manifest;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.aries.application.VersionRange;
+import org.apache.aries.application.impl.VersionRangeImpl;
+import org.apache.aries.application.utils.internal.MessageUtil;
+import org.osgi.framework.Constants;
+
+
+public class ManifestHeaderProcessor
+{
+  public static final String NESTED_FILTER_ATTRIBUTE = "org.apache.aries.application.filter.attribute";
+  private static final Pattern FILTER_ATTR = Pattern.compile("(\\(!)?\\((.*?)([<>]?=)(.*?)\\)\\)?");
+  private static final String LESS_EQ_OP = "<=";
+  private static final String GREATER_EQ_OP = ">=";
+
+  /**
+   * A simple class to associate two types.
+   *
+   * @param <N> The type for the 'Name'
+   * @param <V> The type for the 'Value'
+   */
+  public static class NameValuePair<N,V>{
+    private N name;
+    private V value;
+    public NameValuePair(N name, V value)
+    {
+      this.name = name;
+      this.value = value;
+    }
+    public N getName()
+    {
+      return name;
+    }
+    public void setName(N name)
+    {
+      this.name = name;
+    }
+    public V getValue()
+    {
+      return value;
+    }
+    public void setValue(V value)
+    {
+      this.value = value;
+    }
+    @Override
+    public String toString(){
+      return "{"+name.toString()+"::"+value.toString()+"}";
+    }
+    @Override
+    public int hashCode()
+    {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + ((value == null) ? 0 : value.hashCode());
+      return result;
+    }
+    @Override
+    public boolean equals(Object obj)
+    {
+      if (this == obj) return true;
+      if (obj == null) return false;
+      if (getClass() != obj.getClass()) return false;
+      final NameValuePair<?, ?> other = (NameValuePair<?, ?>) obj;
+      if (name == null) {
+        if (other.name != null) return false;
+      } else if (!name.equals(other.name)) return false;
+      if (value == null) {
+        if (other.value != null) return false;
+      } else if (!value.equals(other.value)) return false;
+      return true;
+    }
+  }
+  
+  /**
+   * Intended to provide a standard way to add Name/Value's to 
+   * aggregations of Name/Value's.
+   *
+   * @param <N> Type of 'Name'
+   * @param <V> Type of 'Value'
+   */
+  public static interface NameValueCollection<N,V>{
+    /**
+     * Add this Name & Value to the collection.
+     * @param n
+     * @param v
+     */
+    public void addToCollection(N n,V v);
+  }
+
+  /**
+   * Map of Name -> Value.
+   * 
+   * @param <N> Type of 'Name'
+   * @param <V> Type of 'Value'
+   */
+  public static class NameValueMap<N,V> extends HashMap<N,V> implements NameValueCollection<N,V>, Map<N,V>{
+	    
+   public void addToCollection(N n,V v){
+      this.put(n,v);
+    }
+   @Override
+   public String toString(){
+      StringBuffer sb = new StringBuffer();
+      sb.append("{");
+      boolean first=true;
+      for(Map.Entry<N, V> entry : this.entrySet()){
+        if(!first)sb.append(",");
+        first=false;
+        sb.append(entry.getKey()+"->"+entry.getValue());
+      }
+      sb.append("}");
+      return sb.toString();
+    }
+  }
+  
+  /**
+   * List of Name/Value
+   *
+   * @param <N> Type of 'Name'
+   * @param <V> Type of 'Value'
+   */
+  public static class NameValueList<N,V> extends ArrayList<NameValuePair<N,V>> implements NameValueCollection<N,V>, List<NameValuePair<N,V>>{    
+
+	public void addToCollection(N n,V v){
+      this.add(new NameValuePair<N,V>(n,v));
+    } 
+	@Override
+    public String toString(){
+      StringBuffer sb = new StringBuffer();
+      sb.append("{");
+      boolean first = true;
+      for(NameValuePair<N, V> nvp : this){
+        if(!first)sb.append(",");
+        first=false;
+        sb.append(nvp.toString());        
+      }
+      sb.append("}");
+      return sb.toString();
+    }
+  }
+  
+  /**
+   * 
+   * Splits a delimiter separated string, tolerating presence of non separator commas
+   * within double quoted segments.
+   * 
+   * Eg.
+   * com.ibm.ws.eba.helloWorldService;version="[1.0.0, 1.0.0]" &
+   * com.ibm.ws.eba.helloWorldService;version="1.0.0"
+   * com.ibm.ws.eba.helloWorld;version="2";bundle-version="[2,30)"
+   * com.acme.foo;weirdAttr="one;two;three";weirdDir:="1;2;3"
+   *  @param value          the value to be split
+   *  @param delimiter      the delimiter string such as ',' etc.
+   *  @return List<String>  the components of the split String in a list
+   */
+  public static List<String> split(String value, String delimiter)
+  {
+    List<String> result = new ArrayList<String>();
+    if (value != null) {
+      String[] packages = value.split(delimiter);
+      
+      for (int i = 0; i < packages.length; ) {
+        String tmp = packages[i++].trim();
+        // if there is a odd number of " in a string, we need to append
+        while (count(tmp, "\"") % 2 != 0) {
+          // check to see if we need to append the next package[i++]          
+            if (i<packages.length)
+              tmp = tmp + delimiter + packages[i++].trim();
+            else 
+              // oops. The double quotes are not paired up. We have reached to the end of the string.
+              throw new IllegalArgumentException(MessageUtil.getMessage("APPUTILS0008E",tmp));        
+        }
+        
+        result.add(tmp);
+        
+      }
+    }
+    return result;
+  }  
+  
+ /**
+  * count the number of characters in a string
+  * @param parent The string to be searched
+  * @param subString The substring to be found
+  * @return the number of occurrence of the subString
+  */
+  private static int count(String parent, String subString) {
+    
+    int count = 0 ;
+    int i = parent.indexOf(subString);
+    while (i > -1) {
+      if (parent.length() >= i+1)
+        parent = parent.substring(i+1);
+      count ++;
+      i = parent.indexOf(subString);
+    }
+    return count;
+  }
+  /**
+   * Internal method to parse headers with the format<p>
+   *   [Name](;[Name])*(;[attribute-name]=[attribute-value])*<br> 
+   * Eg.<br>
+   *   rumplestiltskin;thing=value;other=something<br>
+   *   littleredridinghood
+   *   bundle1;bundle2;other=things
+   *   bundle1;bundle2
+   *   
+   * @param s data to parse
+   * @return a list of NameValuePair, with the Name being the name component, 
+   *         and the Value being a NameValueMap of key->value mappings.   
+   */
+  private static List<NameValuePair<String, NameValueMap<String, String>>> genericNameWithNameValuePairProcess(String s){    
+    String name;
+    NameValueMap<String,String> params = null;
+    List<NameValuePair<String, NameValueMap<String, String>>> nameValues = new ArrayList<NameValuePair<String, NameValueMap<String, String>>>();
+    List<String> pkgs = new ArrayList<String>();
+    int index = s.indexOf(";");
+    if(index==-1){
+      name = s;
+      params = new NameValueMap<String, String>();
+      pkgs.add(name);
+    }else{       
+      name = s.substring(0,index).trim();
+      String tail = s.substring(index+1).trim();
+      
+      pkgs.add(name); // add the first package
+      StringBuilder parameters = new StringBuilder();
+          
+      
+      // take into consideration of multiple packages separated by ';'
+      // while they share the same attributes or directives
+      List<String> tailParts = split(tail, ";");
+      boolean firstParameter =false;
+      
+      for (String part : tailParts) {
+        // if it is not a parameter and no parameter appears in front of it, it must a package
+        if (!!!(part.contains("=")))  {
+          // Need to make sure no parameter appears before the package, otherwise ignore this string
+          // as this syntax is invalid
+          if (!!!(firstParameter))
+            pkgs.add(part);
+        } else {
+          if (!!!(firstParameter)) 
+            firstParameter = true;
+
+          parameters.append(part + ";");
+        }
+      }          
+      
+      if (parameters.length() != 0) {
+        //remove the final ';' if there is one
+        if (parameters.toString().endsWith(";")) {
+         
+          parameters = parameters.deleteCharAt(parameters.length() -1);
+        }       
+        
+        params = genericNameValueProcess(parameters.toString());
+      }
+      
+    }
+    for (String pkg : pkgs) {
+      nameValues.add(new NameValuePair<String, NameValueMap<String, String>>(pkg,params));
+    }  
+    
+    return nameValues;
+   
+  }
+
+  /**
+   * Internal method to parse headers with the format<p>
+   *   [attribute-name]=[attribute-value](;[attribute-name]=[attribute-value])*<br>
+   * Eg.<br>
+   *   thing=value;other=something<br>
+   * <p>
+   * Note. Directives (name:=value) are represented in the map with name suffixed by ':'
+   *   
+   * @param s data to parse
+   * @return a NameValueMap, with attribute-name -> attribute-value.
+   */
+  private static NameValueMap<String,String> genericNameValueProcess(String s){
+    NameValueMap<String,String> params = new NameValueMap<String,String>();  
+    List<String> parameters = split(s, ";");
+    for(String parameter : parameters) {
+      List<String> parts = split(parameter,"=");
+      // do a check, otherwise we might get NPE   
+      if (parts.size() ==2) {
+        String second = parts.get(1).trim();
+        if (second.startsWith("\"") && second.endsWith("\""))
+          second = second.substring(1,second.length()-1);
+        params.put(parts.get(0).trim(), second);
+      }
+    }
+
+    return params;
+  }
+  
+  /**
+   * Processes an import/export style header.. <p> 
+   *  pkg1;attrib=value;attrib=value,pkg2;attrib=value,pkg3;attrib=value
+   * 
+   * @param out The collection to add each package name + attrib map to.
+   * @param s The data to parse
+   */
+  private static void genericImportExportProcess(NameValueCollection<String, NameValueMap<String,String>>out, String s){
+    List<String> packages = split(s, ",");
+    for(String pkg : packages){   
+      List<NameValuePair<String, NameValueMap<String, String>>> ps = genericNameWithNameValuePairProcess(pkg);
+      for (NameValuePair<String, NameValueMap<String, String>> p : ps) {
+        out.addToCollection(p.getName(), p.getValue());
+      }
+    }    
+  }
+  
+  /**
+   * Parse an export style header.<p>
+   *   pkg1;attrib=value;attrib=value,pkg2;attrib=value,pkg3;attrib=value2
+   * <p>
+   * Result is returned as a list, as export does allow duplicate package exports.
+   * 
+   * @param list The data to parse.
+   * @return List of NameValuePairs, where each Name in the list is an exported package, 
+   *         with its associated Value being a NameValueMap of any attributes declared. 
+   */
+  public static List<NameValuePair<String, NameValueMap<String, String>>> parseExportString(String s){
+    NameValueList<String, NameValueMap<String, String>> retval = new NameValueList<String, NameValueMap<String, String>>();
+    genericImportExportProcess(retval, s);
+    return retval;
+  }
+  
+  /**
+   * Parse an export style header in a list.<p>
+   *   pkg1;attrib=value;attrib=value
+   *   pkg2;attrib=value
+   *   pkg3;attrib=value2
+   * <p>
+   * Result is returned as a list, as export does allow duplicate package exports.
+   * 
+   * @param list The data to parse.
+   * @return List of NameValuePairs, where each Name in the list is an exported package, 
+   *         with its associated Value being a NameValueMap of any attributes declared. 
+   */
+  public static List<NameValuePair<String, NameValueMap<String, String>>> parseExportList(List<String> list){
+    NameValueList<String, NameValueMap<String, String>> retval = new NameValueList<String, NameValueMap<String, String>>();
+    for(String pkg : list){   
+      List<NameValuePair<String, NameValueMap<String, String>>> ps = genericNameWithNameValuePairProcess(pkg);
+      for (NameValuePair<String, NameValueMap<String, String>> p : ps) {
+        retval.addToCollection(p.getName(), p.getValue());
+      }
+    } 
+    return retval;
+  }
+  
+  /**
+   * Parse an import style header.<p>
+   *   pkg1;attrib=value;attrib=value,pkg2;attrib=value,pkg3;attrib=value
+   * <p>
+   * Result is returned as a set, as import does not allow duplicate package imports.
+   * 
+   * @param s The data to parse.
+   * @return Map of NameValuePairs, where each Key in the Map is an imported package, 
+   *         with its associated Value being a NameValueMap of any attributes declared. 
+   */  
+  public static Map<String, NameValueMap<String, String>> parseImportString(String s){
+    NameValueMap<String, NameValueMap<String, String>> retval = new NameValueMap<String, NameValueMap<String, String>>();
+    genericImportExportProcess(retval, s);
+    return retval;    
+  }
+  
+  /**
+   * Parse a bundle symbolic name.<p>
+   *   bundlesymbolicname;attrib=value;attrib=value
+   * <p>
+   * 
+   * @param s The data to parse.
+   * @return NameValuePair with Name being the BundleSymbolicName, 
+   *         and Value being any attribs declared for the name. 
+   */   
+  public static NameValuePair<String, NameValueMap<String, String>> parseBundleSymbolicName(String s){
+    return genericNameWithNameValuePairProcess(s).get(0); // should just return the first one
+  }
+
+  /**
+   * Parse a version range.. 
+   * 
+   * @param s
+   * @return VersionRange object.
+   * @throws IllegalArgumentException if the String could not be parsed as a VersionRange
+   */
+  public static VersionRange parseVersionRange(String s) throws IllegalArgumentException{
+    return new VersionRangeImpl(s);
+  }
+  
+  /**
+   * Parse a version range and indicate if the version is an exact version 
+   * 
+   * @param s
+   * @param exactVersion
+   * @return VersionRange object.
+   * @throws IllegalArgumentException if the String could not be parsed as a VersionRange
+   */
+  public static VersionRange parseVersionRange(String s, boolean exactVersion) throws IllegalArgumentException{
+    return new VersionRangeImpl(s, exactVersion);
+  }
+
+  /**
+	 * Generate a filter from a set of attributes. This filter will be suitable
+	 * for presentation to OBR This means that, due to the way OBR works, it
+	 * will include a stanza of the form, (mandatory:<*mandatoryAttribute)
+	 * Filter strings generated by this method will therefore tend to break the
+	 * standard OSGi Filter class. The OBR stanza can be stripped out later if
+	 * required.
+	 * 
+	 * @param attribs
+	 * @return filter string
+	 */
+	public static String generateFilter(Map<String, String> attribs) {
+		StringBuilder filter = new StringBuilder("(&");
+		boolean realAttrib = false;
+		StringBuffer realAttribs = new StringBuffer();
+
+		if (attribs == null) {
+			attribs = new HashMap<String, String>();
+		}
+
+		for (Map.Entry<String, String> attrib : attribs.entrySet()) {
+			String attribName = attrib.getKey();
+
+			if (attribName.endsWith(":")) {
+				// skip all directives. It is used to affect the attribs on the
+				// filter xml.
+			} else if ((Constants.VERSION_ATTRIBUTE.equals(attribName))
+					|| (Constants.BUNDLE_VERSION_ATTRIBUTE.equals(attribName))) {
+				// version and bundle-version attrib requires special
+				// conversion.
+				realAttrib = true;
+
+				VersionRange vr = ManifestHeaderProcessor
+						.parseVersionRange(attrib.getValue());
+
+				// The RFC of OSGi Bundle Repository (OBR) is still under discussion.
+				// According to section "5.11.3 Require-Bundle" in the current version 
+				// of this RFC (Jan. 2011), we need use version rather than bundle-version
+				// in the filter for the "require-bundle" requirement.
+				String versionAttribName = "version";
+				
+				filter.append("(" + versionAttribName + ">=" + vr.getMinimumVersion());
+
+				if (vr.getMaximumVersion() != null) {
+					filter.append(")(" + versionAttribName + "<=");
+					filter.append(vr.getMaximumVersion());
+				}
+
+				if (vr.getMaximumVersion() != null && vr.isMinimumExclusive()) {
+					filter.append(")(!(" + versionAttribName + "=");
+					filter.append(vr.getMinimumVersion());
+					filter.append(")");
+				}
+
+				if (vr.getMaximumVersion() != null && vr.isMaximumExclusive()) {
+					filter.append(")(!(" + versionAttribName + "=");
+					filter.append(vr.getMaximumVersion());
+					filter.append(")");
+				}
+				filter.append(")");
+
+			} else if (NESTED_FILTER_ATTRIBUTE.equals(attribName)) {
+				// Filters go in whole, no formatting needed
+				realAttrib = true;
+				filter.append(attrib.getValue());
+
+			} else if (Constants.OBJECTCLASS.equals(attribName)) {
+				realAttrib = true;
+				// objectClass has a "," separated list of interfaces
+				String[] values = attrib.getValue().split(",");
+				for (String s : values)
+					filter.append("(" + Constants.OBJECTCLASS + "=" + s + ")");
+
+			} else {
+				// attribName was not version..
+				realAttrib = true;
+
+				filter.append("(" + attribName + "=" + attrib.getValue() + ")");
+				// store all attributes in order to build up the mandatory
+				// filter and separate them with ", "
+				// skip bundle-symbolic-name in the mandatory directive query
+				if (!!!Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE
+						.equals(attribName)) {
+					realAttribs.append(attribName);
+					realAttribs.append(", ");
+				}
+			}
+		}
+		/*
+		 * The following is how OBR makes mandatory attributes work, we require
+		 * that the set of mandatory attributes on the export is a subset of (or
+		 * equal to) the set of the attributes we supply.
+		 */
+
+		if (realAttribs.length() > 0) {
+			String attribStr = (realAttribs.toString()).trim();
+			// remove the final ,
+			if ((attribStr.length() > 0) && (attribStr.endsWith(","))) {
+				attribStr = attribStr.substring(0, attribStr.length() - 1);
+			}
+			// build the mandatory filter, e.g.(mandatory:&lt;*company, local)
+			filter.append("(" + Constants.MANDATORY_DIRECTIVE + ":" + "<*"
+					+ attribStr + ")");
+		}
+
+		// Prune (& off the front and ) off end
+		String filterString = filter.toString();
+		int openBraces = 0;
+		for (int i = 0; openBraces < 3; i++) {
+			i = filterString.indexOf('(', i);
+			if (i == -1) {
+				break;
+			} else {
+				openBraces++;
+			}
+		}
+		if (openBraces < 3 && filterString.length() > 2) {
+			filter.delete(0, 2);
+		} else {
+			filter.append(")");
+		}
+
+		String result = "";
+		if (realAttrib != false) {
+			result = filter.toString();
+		}
+		return result;
+	}
+
+	/**
+   * Generate a filter from a set of attributes. This filter will be suitable
+   * for presentation to OBR. This means that, due to the way OBR works, it will
+   * include a stanza of the form, (mandatory:<*mandatoryAttribute) Filter
+   * strings generated by this method will therefore tend to break the standard
+   * OSGi Filter class. The OBR stanza can be stripped out later if required.
+   * 
+   * We may wish to consider relocating this method since VersionRange has its
+   * own top level class.
+   * 
+   * @param type
+   * @param name
+   * @param attribs
+   * @return filter string
+   */
+  public static String generateFilter(String type, String name,
+      Map<String, String> attribs) {
+    StringBuffer filter = new StringBuffer();
+    String result;
+    // shortcut for the simple case with no attribs.
+
+    if (attribs == null || attribs.isEmpty())
+      filter.append("(" + type + "=" + name + ")");
+    else {
+      // process all the attribs passed.
+      // find out whether there are attributes on the filter
+
+      filter.append("(&(" + type + "=" + name + ")");
+
+      String filterString = generateFilter(attribs);
+
+      int start = 0;
+      int end = filterString.length();
+      if (filterString.startsWith("(&")) {
+        start = 2;
+        end--;
+      }
+
+      if ("".equals(filterString)) {
+        filter.delete(0, 2);
+      } else {
+        filter.append(filterString, start, end);
+        filter.append(")");
+      }
+    }
+
+    result = filter.toString();
+
+    return result;
+  }
+
+  private static Map<String, String> parseFilterList(String filter) {
+
+    Map<String, String> result = new HashMap<String, String>();
+    Set<String> negatedVersions = new HashSet<String>();
+    Set<String> negatedBundleVersions = new HashSet<String>();
+
+    String lowerVersion = null;
+    String upperVersion = null;
+    String lowerBundleVersion = null;
+    String upperBundleVersion = null;
+
+    Matcher m = FILTER_ATTR.matcher(filter);
+    while (m.find()) {
+      boolean negation = m.group(1) != null;
+      String attr = m.group(2);
+      String op = m.group(3);
+      String value = m.group(4);
+
+      if (Constants.VERSION_ATTRIBUTE.equals(attr)) {
+        if (negation) {
+          negatedVersions.add(value);
+        } else {
+          if (GREATER_EQ_OP.equals(op))
+            lowerVersion = value;
+          else if (LESS_EQ_OP.equals(op))
+            upperVersion = value;
+          else
+            throw new IllegalArgumentException();
+        }
+      } else if (Constants.BUNDLE_VERSION_ATTRIBUTE.equals(attr)) {
+        // bundle-version is like version, but may be specified at the
+        // same time
+        // therefore we have similar code with separate variables
+        if (negation) {
+          negatedBundleVersions.add(value);
+        } else {
+          if (GREATER_EQ_OP.equals(op))
+            lowerBundleVersion = value;
+          else if (LESS_EQ_OP.equals(op))
+            upperBundleVersion = value;
+          else
+            throw new IllegalArgumentException();
+        }
+      } else {
+        result.put(attr, value);
+      }
+    }
+
+    if (lowerVersion != null) {
+      StringBuilder versionAttr = new StringBuilder(lowerVersion);
+      if (upperVersion != null) {
+        versionAttr.append(",").append(upperVersion).insert(0,
+            negatedVersions.contains(lowerVersion) ? '(' : '[').append(
+            negatedVersions.contains(upperVersion) ? ')' : ']');
+      }
+
+      result.put(Constants.VERSION_ATTRIBUTE, versionAttr.toString());
+    }
+    // Do it again for bundle-version
+    if (lowerBundleVersion != null) {
+      StringBuilder versionAttr = new StringBuilder(lowerBundleVersion);
+      if (upperBundleVersion != null) {
+        versionAttr.append(",").append(upperBundleVersion).insert(0,
+            negatedBundleVersions.contains(lowerBundleVersion) ? '(' : '[')
+            .append(
+                negatedBundleVersions.contains(upperBundleVersion) ? ')' : ']');
+      }
+
+      result.put(Constants.BUNDLE_VERSION_ATTRIBUTE, versionAttr.toString());
+    }
+
+    return result;
+  }
+	  
+  public static Map<String,String> parseFilter(String filter) 
+  {
+    Map<String,String> result;
+    if (filter.startsWith("(&")) {
+      result = parseFilterList(filter.substring(2, filter.length()-1));
+    } else {
+      result = parseFilterList(filter);
+    }
+    return result;
+  }
+}
+

Added: aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestProcessor.java
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestProcessor.java?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestProcessor.java (added)
+++ aries/tags/application-0.2.1/application-utils/src/main/java/org/apache/aries/application/utils/manifest/ManifestProcessor.java Sun Feb 27 20:02:48 2011
@@ -0,0 +1,201 @@
+/*
+ * 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 WARRANTIESOR 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.aries.application.utils.manifest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.apache.aries.application.filesystem.IDirectory;
+import org.apache.aries.application.filesystem.IFile;
+
+/**
+ * This class contains utilities for parsing manifests. It provides methods to
+ * parse the manifest, read a manifest into a map and to split an manifest
+ * entry that follows the Import-Package syntax.
+ */
+public class ManifestProcessor
+{
+  /**
+   * Reads a manifest's main attributes into a String->String map.
+   * <p>
+   * Will always return a map, empty if the manifest had no attributes.
+   * 
+   * @param mf The manifest to read.
+   * @return Map of manifest main attributes.
+   */
+  public static Map<String, String> readManifestIntoMap(Manifest mf){
+
+    HashMap<String, String> props = new HashMap<String, String>();
+    
+    Attributes mainAttrs = mf.getMainAttributes();
+    if (mainAttrs!=null){
+      Set<Entry<Object, Object>> attributeSet =  mainAttrs.entrySet(); 
+      if (attributeSet != null){
+        // Copy all the manifest headers across. The entry set should be a set of
+        // Name to String mappings, by calling String.valueOf we do the conversion
+        // to a string and we do not NPE.
+        for (Map.Entry<Object, Object> entry : attributeSet) {
+          props.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
+        }
+      }    
+    }
+         
+    return props;
+  }
+  
+  /**
+   * This method parses the manifest using a custom manifest parsing routine.
+   * This means that we can avoid the 76 byte line length in a manifest providing
+   * a better developer experience.
+   * 
+   * @param in the input stream to read the manifest from.
+   * @return   the parsed manifest
+   * @throws IOException
+   */
+  public static Manifest parseManifest(InputStream in) throws IOException
+  {
+    Manifest man = new Manifest();
+    
+    // I'm assuming that we use UTF-8 here, but the jar spec doesn't say.
+    BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+    
+    String line;
+    StringBuilder attribute = null;
+    
+    String namedAttribute = null;
+    
+    do {
+      line = reader.readLine();
+
+      // if we get a blank line skip to the next one
+      if (line != null && line.trim().length() == 0) continue;
+      if (line != null && line.charAt(0) == ' ' && attribute != null) {
+        // we have a continuation line, so add to the builder, ignoring the
+        // first character
+        attribute.append(line.trim());
+      } else if (attribute == null) {
+        attribute = new StringBuilder(line.trim());
+      } else if (attribute != null) {
+        // We have fully parsed an attribute
+        int index = attribute.indexOf(":");
+        String attributeName = attribute.substring(0, index).trim();
+        // TODO cope with index + 1 being after the end of attribute
+        String attributeValue = attribute.substring(index + 1).trim();
+        
+        if ("Name".equals(attributeName)) {
+          man.getEntries().put(attributeValue, new Attributes());
+          namedAttribute = attributeValue;
+        } else {
+          if (namedAttribute == null) {
+            man.getMainAttributes().put(new Attributes.Name(attributeName), attributeValue);
+          } else {
+            man.getAttributes(namedAttribute).put(new Attributes.Name(attributeName), attributeValue);
+          }
+        }
+        
+        if (line != null) attribute = new StringBuilder(line.trim());
+      }
+    } while (line != null);
+    
+    return man;
+  }
+  
+  /**
+   * Obtain a manifest from an IDirectory. 
+   * 
+   * @param appDir
+   * @param manifestName the name of manifest
+   * @return Manifest, or null if none found.
+   * @throws IOException
+   */
+  public static Manifest obtainManifestFromAppDir(IDirectory appDir, String manifestName) throws IOException{
+    IFile manifestFile = appDir.getFile(manifestName);
+    Manifest man = null;
+    if (manifestFile != null) {
+      man = parseManifest(manifestFile.open());
+    }
+    return man;
+  }
+
+  
+  /**
+   * 
+   * Splits a delimiter separated string, tolerating presence of non separator commas
+   * within double quoted segments.
+   * 
+   * Eg.
+   * com.ibm.ws.eba.helloWorldService;version="[1.0.0, 1.0.0]" &
+   * com.ibm.ws.eba.helloWorldService;version="1.0.0"
+   * com.ibm.ws.eba.helloWorld;version="2";bundle-version="[2,30)"
+   * com.acme.foo;weirdAttr="one;two;three";weirdDir:="1;2;3"
+   *  @param value          the value to be split
+   *  @param delimiter      the delimiter string such as ',' etc.
+   *  @return List<String>  the components of the split String in a list
+   */
+  public static List<String> split(String value, String delimiter)
+  {
+
+    List<String> result = new ArrayList<String>();
+    if (value != null) {
+      String[] packages = value.split(delimiter);
+      
+      for (int i = 0; i < packages.length; ) {
+        String tmp = packages[i++].trim();
+        // if there is a odd number of " in a string, we need to append
+        while (count(tmp, "\"") % 2 == 1) {
+          // check to see if we need to append the next package[i++]
+          tmp = tmp + delimiter + packages[i++].trim();
+        }
+        
+        result.add(tmp);
+      }
+    }
+
+    return result;
+  }  
+  
+  /**
+   * count the number of characters in a string
+   * @param parent The string to be searched
+   * @param subString The substring to be found
+   * @return the number of occurrence of the subString
+   */
+   private static int count(String parent, String subString) {
+     
+     int count = 0 ;
+     int i = parent.indexOf(subString);
+     while (i > -1) {
+       if (parent.length() >= i+1)
+         parent = parent.substring(i+1);
+       count ++;
+       i = parent.indexOf(subString);
+     }
+     return count;
+   }  
+}

Added: aries/tags/application-0.2.1/application-utils/src/main/resources/OSGI-INF/blueprint/app-utils.xml
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/main/resources/OSGI-INF/blueprint/app-utils.xml?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/main/resources/OSGI-INF/blueprint/app-utils.xml (added)
+++ aries/tags/application-0.2.1/application-utils/src/main/resources/OSGI-INF/blueprint/app-utils.xml Sun Feb 27 20:02:48 2011
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+  
+  <bean id="applicationMf-factory" class="org.apache.aries.application.impl.ApplicationMetadataFactoryImpl" />
+  <service interface="org.apache.aries.application.ApplicationMetadataFactory" ref="applicationMf-factory" />
+  
+  <bean id="deploymentMf-factory" class="org.apache.aries.application.impl.DeploymentMetadataFactoryImpl" />
+  <service interface="org.apache.aries.application.DeploymentMetadataFactory" ref="deploymentMf-factory" />
+  
+</blueprint>

Added: aries/tags/application-0.2.1/application-utils/src/main/resources/org/apache/aries/application/utils/messages/APPUTILSmessages.properties
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/main/resources/org/apache/aries/application/utils/messages/APPUTILSmessages.properties?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/main/resources/org/apache/aries/application/utils/messages/APPUTILSmessages.properties (added)
+++ aries/tags/application-0.2.1/application-utils/src/main/resources/org/apache/aries/application/utils/messages/APPUTILSmessages.properties Sun Feb 27 20:02:48 2011
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+APPUTILS0004E=APPUTILS0004E: Unable to create ContentImpl object based on content: {0}.
+APPUTILS0007E=APPUTILS0007E: The path {0} does not denote a valid file.
+APPUTILS0008E=APPUTILS0008E: Unable to parse the string, because one of the quotations marks (") is missing: {0}.
+APPUTILS0012E=APPUTILS0012E: Unable to create temporary output directory for file {0}.

Added: aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/ApplicationMetadataImplTest.java
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/ApplicationMetadataImplTest.java?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/ApplicationMetadataImplTest.java (added)
+++ aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/ApplicationMetadataImplTest.java Sun Feb 27 20:02:48 2011
@@ -0,0 +1,40 @@
+/*
+ * 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 WARRANTIESOR 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.aries.application.impl;
+
+import java.io.IOException;
+
+import junit.framework.Assert;
+
+import org.apache.aries.application.ApplicationMetadata;
+import org.apache.aries.application.ApplicationMetadataFactory;
+import org.apache.aries.application.impl.ApplicationMetadataFactoryImpl;
+import org.junit.Test;
+
+public class ApplicationMetadataImplTest
+{
+  @Test
+  public void testBasicMetadataCreation() throws IOException
+  {
+    ApplicationMetadataFactory manager = new ApplicationMetadataFactoryImpl();
+    ApplicationMetadata app = manager.parseApplicationMetadata(getClass().getResourceAsStream("/META-INF/APPLICATION.MF"));
+    
+    Assert.assertEquals("Travel Reservation", app.getApplicationName());
+  }
+}

Added: aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/DeploymentContentImplTest.java
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/DeploymentContentImplTest.java?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/DeploymentContentImplTest.java (added)
+++ aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/DeploymentContentImplTest.java Sun Feb 27 20:02:48 2011
@@ -0,0 +1,65 @@
+/*
+ * 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 WARRANTIESOR 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.aries.application.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.aries.application.VersionRange;
+import org.junit.Test;
+import org.osgi.framework.Version;
+
+public class DeploymentContentImplTest {
+  
+  @Test
+  public void testDeploymentContent001() throws Exception {
+    DeploymentContentImpl dc = new DeploymentContentImpl("com.travel.reservation.web;deployed-version=\"1.1.0\"");
+    assertEquals("1.1.0", dc.getAttribute("deployed-version"));
+    VersionRange vi = dc.getVersion();
+    assertTrue(vi.isExactVersion());
+    assertEquals(new Version("1.1.0"), dc.getExactVersion());
+    assertEquals("com.travel.reservation.web", dc.getContentName());
+    assertEquals("{deployed-version->1.1.0}", dc.getNameValueMap().toString());
+  }
+  
+  @Test
+  public void testDeploymentContent002() throws Exception {
+    DeploymentContentImpl dc = new DeploymentContentImpl("com.travel.reservation.business;deployed-version=2.0");
+    assertEquals("2.0", dc.getAttribute("deployed-version"));
+    VersionRange vi = dc.getVersion();
+    assertTrue(vi.isExactVersion());
+    assertEquals(new Version("2.0"), dc.getExactVersion());
+    assertEquals("com.travel.reservation.business", dc.getContentName());
+    assertEquals("{deployed-version->2.0}", dc.getNameValueMap().toString());
+  }
+  
+  
+  @Test
+  public void testDeploymentContent003() throws Exception {
+    DeploymentContentImpl dc = new DeploymentContentImpl("com.travel.reservation.data;deployed-version=2.1.1");
+    assertEquals("2.1.1", dc.getAttribute("deployed-version"));
+    VersionRange vi = dc.getVersion();
+    assertTrue(vi.isExactVersion());
+    assertEquals(new Version("2.1.1"), dc.getExactVersion());
+    assertEquals("com.travel.reservation.data", dc.getContentName());
+    assertEquals("{deployed-version->2.1.1}", dc.getNameValueMap().toString());
+  }
+}
+

Added: aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/VersionRangeTest.java
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/VersionRangeTest.java?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/VersionRangeTest.java (added)
+++ aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/impl/VersionRangeTest.java Sun Feb 27 20:02:48 2011
@@ -0,0 +1,296 @@
+/*
+ * 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 WARRANTIESOR 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.aries.application.impl;
+
+import static org.junit.Assert.*;
+
+import org.apache.aries.application.VersionRange;
+import org.junit.Test;
+import org.osgi.framework.Version;
+
+public class VersionRangeTest
+{
+  /**
+   * Test the version range created correctly
+   * @throws Exception
+   */
+  
+  @Test
+  public void testVersionRange() throws Exception {
+    String version1 = "[1.2.3, 4.5.6]";
+    String version2="(1, 2]";
+    String version3="[2,4)";
+    String version4="(1,2)";
+    String version5="2";
+    String version6 = "2.3";
+    String version7="[1.2.3.q, 2.3.4.p)";
+    String version8="1.2.2.5";
+    String version9="a.b.c";
+    String version10=null;
+    String version11="";
+    String version12="\"[1.2.3, 4.5.6]\"";
+    
+    VersionRange vr = new VersionRangeImpl(version1);
+    assertEquals("The value is wrong", "1.2.3", vr.getMinimumVersion().toString());
+    assertFalse("The value is wrong", vr.isMinimumExclusive());
+    assertEquals("The value is wrong", "4.5.6", vr.getMaximumVersion().toString());
+    assertFalse("The value is wrong", vr.isMaximumExclusive());
+    
+    vr = new VersionRangeImpl(version2);
+    assertEquals("The value is wrong", "1.0.0", vr.getMinimumVersion().toString());
+    assertTrue("The value is wrong", vr.isMinimumExclusive());
+    assertEquals("The value is wrong", "2.0.0", vr.getMaximumVersion().toString());
+    assertFalse("The value is wrong", vr.isMaximumExclusive());
+    
+    vr = new VersionRangeImpl(version3);
+    
+    assertEquals("The value is wrong", "2.0.0", vr.getMinimumVersion().toString());
+    assertFalse("The value is wrong", vr.isMinimumExclusive());
+    assertEquals("The value is wrong", "4.0.0", vr.getMaximumVersion().toString());
+    assertTrue("The value is wrong", vr.isMaximumExclusive());
+    
+    vr = new VersionRangeImpl(version4);
+    
+    assertEquals("The value is wrong", "1.0.0", vr.getMinimumVersion().toString());
+    assertTrue("The value is wrong", vr.isMinimumExclusive());
+    assertEquals("The value is wrong", "2.0.0", vr.getMaximumVersion().toString());
+    assertTrue("The value is wrong", vr.isMaximumExclusive());
+    
+    vr = new VersionRangeImpl(version5);
+    assertEquals("The value is wrong", "2.0.0", vr.getMinimumVersion().toString());
+    assertFalse("The value is wrong", vr.isMinimumExclusive());
+    assertNull("The value is wrong", vr.getMaximumVersion());
+    assertFalse("The value is wrong", vr.isMaximumExclusive());
+    
+    vr = new VersionRangeImpl(version6);
+    assertEquals("The value is wrong", "2.3.0", vr.getMinimumVersion().toString());
+    assertFalse("The value is wrong", vr.isMinimumExclusive());
+    assertNull("The value is wrong", vr.getMaximumVersion());
+    assertFalse("The value is wrong", vr.isMaximumExclusive());
+    
+    vr = new VersionRangeImpl(version7);
+    assertEquals("The value is wrong", "1.2.3.q", vr.getMinimumVersion().toString());
+    assertFalse("The value is wrong", vr.isMinimumExclusive());
+    assertEquals("The value is wrong", "2.3.4.p", vr.getMaximumVersion().toString());
+    assertTrue("The value is wrong", vr.isMaximumExclusive());
+    
+    vr = new VersionRangeImpl(version8);
+    assertEquals("The value is wrong", "1.2.2.5", vr.getMinimumVersion().toString());
+    assertFalse("The value is wrong", vr.isMinimumExclusive());
+    assertNull("The value is wrong", vr.getMaximumVersion());
+    assertFalse("The value is wrong", vr.isMaximumExclusive());
+    boolean exception = false;
+    try {
+    vr = new VersionRangeImpl(version9);
+    } catch (Exception e){
+      exception = true;
+    }
+    
+    assertTrue("The value is wrong", exception);
+    boolean exceptionNull = false;
+    try {
+      vr = new VersionRangeImpl(version10);
+    } catch (Exception e){
+      exceptionNull = true;
+    }
+    assertTrue("The value is wrong", exceptionNull);
+    // empty version should be defaulted to >=0.0.0
+    vr = new VersionRangeImpl(version11);
+    assertEquals("The value is wrong", "0.0.0", vr.getMinimumVersion().toString());
+    assertFalse("The value is wrong", vr.isMinimumExclusive());
+    assertNull("The value is wrong", vr.getMaximumVersion());
+    assertFalse("The value is wrong", vr.isMaximumExclusive());
+
+    vr = new VersionRangeImpl(version12);
+    assertEquals("The value is wrong", "1.2.3", vr.getMinimumVersion().toString());
+    assertFalse("The value is wrong", vr.isMinimumExclusive());
+    assertEquals("The value is wrong", "4.5.6", vr.getMaximumVersion().toString());
+    assertFalse("The value is wrong", vr.isMaximumExclusive());  
+  }
+  
+  @Test
+  public void testInvalidVersions() throws Exception
+  {
+    try {
+      new VersionRangeImpl("a");
+      assertTrue("Should have thrown an exception", false);
+    } catch (IllegalArgumentException e) {
+    }
+    
+    try {
+      new VersionRangeImpl("[1.0.0,1.0.1]", true);
+      assertTrue("Should have thrown an exception", false);
+    } catch (IllegalArgumentException e) {
+    }
+  }
+  
+  @Test
+  public void testExactVersion() throws Exception 
+  {
+    VersionRange vr;
+    try {
+      vr = new VersionRangeImpl("[1.0.0, 2.0.0]", true);
+      fail("from 1 to 2 not excludsive is not an exact range");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+    
+    vr = new VersionRangeImpl("[1.0.0, 1.0.0]", true);
+    assertTrue(vr.isExactVersion());
+    
+    try {
+      vr = new VersionRangeImpl("(1.0.0, 1.0.0]", true);
+      fail("from 1 (not including 1) to 1, is not valid");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+    
+    try {
+      vr = new VersionRangeImpl("[1.0.0, 1.0.0)", true);
+      fail("sfrom 1 to 1 (not including 1), is not valid");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    vr = new VersionRangeImpl("1.0.0", true);
+    assertTrue(vr.isExactVersion());
+
+    vr = new VersionRangeImpl("1.0.0", false);
+    assertFalse(vr.isExactVersion());
+
+    vr = new VersionRangeImpl("[1.0.0, 2.0.0]");
+    assertFalse(vr.isExactVersion());
+    
+    vr = new VersionRangeImpl("[1.0.0, 1.0.0]");
+    assertTrue(vr.isExactVersion());
+
+    vr = new VersionRangeImpl("1.0.0", true);
+    assertEquals(new Version("1.0.0"), vr.getMinimumVersion());
+    assertTrue(vr.isExactVersion());
+    
+    vr = new VersionRangeImpl("1.0.0", false);
+    assertEquals(new Version("1.0.0"), vr.getMinimumVersion());
+    assertNull(vr.getMaximumVersion());
+    assertFalse(vr.isExactVersion());
+    
+    // don't throw any silly exceptions
+    vr = new VersionRangeImpl("[1.0.0,2.0.0)", false);
+    assertFalse(vr.isExactVersion());
+    
+    vr = new VersionRangeImpl("[1.0.0, 2.0.0]");
+    assertFalse(vr.isExactVersion());
+
+    vr = new VersionRangeImpl("[1.0.0, 1.0.0]");
+    assertTrue(vr.isExactVersion());
+
+  }
+  
+  @Test
+  public void testMatches()
+  {
+    VersionRange vr = new VersionRangeImpl("[1.0.0, 2.0.0]");
+    
+    assertFalse(vr.matches(new Version(0,9,0)));
+    assertFalse(vr.matches(new Version(2,1,0)));
+    assertTrue(vr.matches(new Version(2,0,0)));
+    assertTrue(vr.matches(new Version(1,0,0)));
+    assertTrue(vr.matches(new Version(1,5,0)));
+    
+    vr = new VersionRangeImpl("[1.0.0, 2.0.0)");
+    
+    assertFalse(vr.matches(new Version(0,9,0)));
+    assertFalse(vr.matches(new Version(2,1,0)));
+    assertFalse(vr.matches(new Version(2,0,0)));
+    assertTrue(vr.matches(new Version(1,0,0)));
+    assertTrue(vr.matches(new Version(1,5,0)));
+
+    vr = new VersionRangeImpl("(1.0.0, 2.0.0)");
+    
+    assertFalse(vr.matches(new Version(0,9,0)));
+    assertFalse(vr.matches(new Version(2,1,0)));
+    assertFalse(vr.matches(new Version(2,0,0)));
+    assertFalse(vr.matches(new Version(1,0,0)));
+    assertTrue(vr.matches(new Version(1,5,0)));
+
+    vr = new VersionRangeImpl("[1.0.0, 1.0.0]");
+    assertFalse(vr.matches(new Version(0,9,0)));
+    assertFalse(vr.matches(new Version(2,0,0)));
+    assertTrue(vr.matches(new Version(1,0,0)));
+    assertFalse(vr.matches(new Version(1,5,0)));
+    assertFalse(vr.matches(new Version(1,9,9)));
+  }
+  
+  @Test
+  public void testIntersectVersionRange_Valid1()
+  {
+    VersionRange v1 = new VersionRangeImpl("[1.0.0,3.0.0]");
+    VersionRange v2 = new VersionRangeImpl("[2.0.0,3.0.0)");
+    VersionRange result = v1.intersect(v2);
+    assertNotNull(result);
+    assertEquals("[2.0.0,3.0.0)", result.toString());
+  }
+  
+  @Test
+  public void testIntersectVersionRange_Valid2()
+  {
+    VersionRange v1 = new VersionRangeImpl("[1.0.0,3.0.0)");
+    VersionRange v2 = new VersionRangeImpl("(2.0.0,3.0.0]");
+    VersionRange result = v1.intersect(v2);
+    assertNotNull(result);
+    assertEquals("(2.0.0,3.0.0)", result.toString());
+  }
+
+  @Test
+  public void testIntersectVersionRange_Valid3()
+  {
+    VersionRange v1 = new VersionRangeImpl("[2.0.0,2.0.0]");
+    VersionRange v2 = new VersionRangeImpl("[1.0.0,3.0.0]");
+    VersionRange result = v1.intersect(v2);
+    assertNotNull(result);
+    assertEquals("[2.0.0,2.0.0]", result.toString());
+  }
+  
+  @Test
+  public void testIntersectVersionRange_Invalid1()
+  {
+    VersionRange v1 = new VersionRangeImpl("[1.0.0,2.0.0]");
+    VersionRange v2 = new VersionRangeImpl("(2.0.0,3.0.0]");
+    VersionRange result = v1.intersect(v2);
+    assertNull(result);
+  }
+
+  @Test
+  public void testIntersectVersionRange_Invalid2()
+  {
+    VersionRange v1 = new VersionRangeImpl("[1.0.0,2.0.0)");
+    VersionRange v2 = new VersionRangeImpl("[2.0.0,3.0.0]");
+    VersionRange result = v1.intersect(v2);
+    assertNull(result);
+  }
+
+  @Test
+  public void testIntersectVersionRange_Invalid3()
+  {
+    VersionRange v1 = new VersionRangeImpl("[1.0.0,1.0.0]");
+    VersionRange v2 = new VersionRangeImpl("[2.0.0,2.0.0]");
+    VersionRange result = v1.intersect(v2);
+    assertNull(result);
+  }
+
+}
\ No newline at end of file

Added: aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/utils/filesystem/FileSystemTest.java
URL: http://svn.apache.org/viewvc/aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/utils/filesystem/FileSystemTest.java?rev=1075127&view=auto
==============================================================================
--- aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/utils/filesystem/FileSystemTest.java (added)
+++ aries/tags/application-0.2.1/application-utils/src/test/java/org/apache/aries/application/utils/filesystem/FileSystemTest.java Sun Feb 27 20:02:48 2011
@@ -0,0 +1,280 @@
+/*
+ * 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 WARRANTIESOR 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.aries.application.utils.filesystem;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.aries.application.filesystem.IDirectory;
+import org.apache.aries.application.filesystem.IFile;
+import org.apache.aries.application.utils.AppConstants;
+import org.apache.aries.unittest.junit.Assert;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+
+/**
+ * This class contains tests for the virtual file system.
+ */
+public class FileSystemTest
+{
+  /**
+   * Make sure we correctly understand the content of the application when the
+   * application is an exploded directory. This test just checks that the
+   * root directory returns the expected information.
+   * 
+   * @throws IOException
+   */
+  @Test(expected=UnsupportedOperationException.class)
+  public void basicRootDirTestsWithFiles() throws IOException
+  {
+    File baseDir = new File("../src/test/resources/app1");
+    File manifest = new File(baseDir, AppConstants.APPLICATION_MF);
+    IDirectory dir = FileSystem.getFSRoot(baseDir);
+    
+    runBasicRootDirTests(dir, baseDir.length(), manifest.lastModified());
+  }
+  
+  /**
+   * Make sure we correctly understand the directory structure for exploded
+   * directories.
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void basicDirTestsWithFiles() throws IOException
+  {
+    File baseDir = new File("../src/test/resources/app1");
+    IDirectory dir = FileSystem.getFSRoot(baseDir);
+
+    File desiredFile = new File(baseDir, AppConstants.APPLICATION_MF);
+    
+    runBasicDirTest(dir, desiredFile.length(), desiredFile.lastModified());
+  }
+  
+  /**
+   * Make sure we correctly understand the content of the application when the
+   * application is a zip. This test just checks that the
+   * root directory returns the expected information.
+   * 
+   * @throws IOException
+   */
+  @Test(expected=UnsupportedOperationException.class)
+  public void basicRootDirTestsWithZip() throws IOException
+  {
+    File baseDir = new File("fileSystemTest/app2.zip");
+    IDirectory dir = FileSystem.getFSRoot(baseDir);
+    
+    runBasicRootDirTests(dir, baseDir.length(), baseDir.lastModified());
+  }
+  
+  /**
+   * Make sure we correctly understand the directory structure for zips.
+   * 
+   * @throws IOException
+   */
+  @Test
+  public void basicDirTestsWithZip() throws IOException
+  {
+    File baseDir = new File("fileSystemTest/app2.zip");
+    IDirectory dir = FileSystem.getFSRoot(baseDir);
+
+    File desiredFile = new File(new File("../src/test/resources/app1"), AppConstants.APPLICATION_MF);
+    
+    runBasicDirTest(dir, desiredFile.length(), desiredFile.lastModified());
+  }
+  
+  /**
+   * Zip up the app1 directory to create a zippped version before running any
+   * tests.
+   * 
+   * @throws IOException
+   */
+  @BeforeClass
+  public static void makeZip() throws IOException
+  {
+    File zipFile = new File("fileSystemTest/app2.zip");
+    zipFile.getParentFile().mkdirs();
+    ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile));
+    
+    int index = new File("../src/test/resources/app1").getAbsolutePath().length();
+    
+    writeEnties(out, new File("../src/test/resources/app1"), index);
+    
+    out.close();
+  }
+  
+  /**
+   * Make sure the test zip is deleted afterwards.
+   */
+  @AfterClass
+  public static void destroyZip()
+  {
+    new File("fileSystemTest/app2.zip").delete();
+    new File("fileSystemTest").delete();
+  }
+  
+  /**
+   * This method writes the given directory into the provided zip output stream.
+   * It removes the first <code>index</code> bytes from the absolute path name
+   * when building the zip.
+   * 
+   * @param zos   the zip output stream to use
+   * @param f     the directory to write into the zip.
+   * @param index how much of the file name to chop off.
+   * @throws IOException
+   */
+  public static void writeEnties(ZipOutputStream zos, File f, int index) throws IOException {
+    File[] files = f.listFiles();
+    
+    if (files != null) {
+      for (File file : files) {
+        String fileName = file.getAbsolutePath().substring(index + 1);
+        
+     // Bug 1954: replace any '\' characters with '/' - required by ZipEntry
+        fileName = fileName.replace('\\', '/');
+        
+        if (file.isDirectory()) fileName = fileName + "/";
+        
+        ZipEntry ze = new ZipEntry(fileName);
+        ze.setSize(file.length());
+        ze.setTime(file.lastModified());
+        zos.putNextEntry(ze);
+        
+        if (file.isFile()) {
+          InputStream is = new FileInputStream(file);
+          byte[] buffer = new byte[(int)file.length()];
+          int len = is.read(buffer);
+          zos.write(buffer, 0, len);
+          is.close();   // Bug 1594
+        }
+
+        zos.closeEntry();
+        
+        if (file.isDirectory()) {
+          writeEnties(zos, file, index);
+        }
+      }
+    }
+  }
+  
+  /**
+   * This method makes sure that the data is correctly understood from disk. It
+   * is called for both the file and zip versions of the test to ensure we have
+   * consistent results.
+   * 
+   * @param dir   The IDirectory for the root of the vFS.
+   * @param len   The size of the file.
+   * @param time  The time the file was last updated.
+   * @throws IOException
+   */
+  public void runBasicRootDirTests(IDirectory dir, long len, long time) throws IOException
+  {
+    assertEquals("The root file system name is not correct", "", dir.getName());
+    assertEquals("The size of the file is not correct", len, dir.getSize());
+    
+    // This assertion just isn't working on Hudson as of build #79
+    // assertEquals("The last modified time of the file is not correct", time, dir.getLastModified());
+    
+    assertNull("I managed to get a parent of a root", dir.getParent());
+    assertTrue("The root dir does not know it is a dir", dir.isDirectory());
+    assertFalse("The root dir has an identity crisis and thinks it is a file", dir.isFile());
+
+    dir.open();
+  }
+  
+  /**
+   * This method makes sure that the data is correctly understood from disk. It
+   * is called for both the file and zip versions of the test to ensure we have
+   * consistent results.
+   * 
+   * @param dir   The IDirectory for the root of the vFS.
+   * @param len   The size of the file.
+   * @param time  The time the file was last updated.
+   * @throws IOException
+   */
+  public void runBasicDirTest(IDirectory dir, long len, long time) throws IOException
+  {
+    assertNull("for some reason our fake app has a fake blueprint file.", dir.getFile("OSGI-INF/blueprint/aries.xml"));
+    
+    IFile file = dir.getFile(AppConstants.APPLICATION_MF);
+    
+    assertNotNull("we could not find the application manifest", file);
+    
+    assertEquals(AppConstants.APPLICATION_MF, file.getName().replace('\\', '/'));
+    assertTrue("The last update time is not within 2 seconds of the expected value. Expected: " + time + " Actual: " + file.getLastModified(), Math.abs(time - file.getLastModified()) < 2000);
+    assertEquals(len, file.getSize());
+    assertEquals("META-INF", file.getParent().getName());
+    assertFalse(file.isDirectory());
+    assertTrue(file.isFile());
+    
+    List<IFile> files = dir.listFiles();
+    Iterator<IFile> it = files.iterator();
+    while (it.hasNext()) { 
+      IFile f = it.next();
+      if (f.getName().equalsIgnoreCase(".svn")) { 
+        it.remove();
+      }
+    }
+    
+    assertEquals(1, files.size());
+    
+    IFile metaInf = files.get(0);
+    
+    assertTrue(metaInf.isDirectory());
+    assertEquals("META-INF", metaInf.getName());
+    assertNotNull(metaInf.convert());
+    
+    for (IFile aFile : dir) {
+      if (!aFile.getName().equalsIgnoreCase(".svn")) { 
+        assertTrue(aFile.isDirectory());
+        assertEquals("META-INF", aFile.getName());
+        assertNotNull(aFile.convert());
+      }
+    }
+    
+    InputStream is = file.open();
+    
+    Manifest man = new Manifest(is);
+    //remember to close the input stream after use
+    is.close();
+    assertEquals("com.travel.reservation", man.getMainAttributes().getValue("Application-SymbolicName"));
+    
+    IFile applicationMF2 = dir.getFile(AppConstants.APPLICATION_MF);
+    
+    Assert.assertEqualsContract(file, applicationMF2, dir);
+    Assert.assertHashCodeEquals(file, applicationMF2, true);
+  }
+}