You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ri...@apache.org on 2005/08/16 20:34:41 UTC

svn commit: r233031 [2/21] - in /incubator/oscar/trunk: ./ etc/ lib/ src/ src/org/ src/org/apache/ src/org/apache/osgi/ src/org/apache/osgi/bundle/ src/org/apache/osgi/bundle/bundlerepository/ src/org/apache/osgi/bundle/bundlerepository/kxmlsax/ src/or...

Added: incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java
URL: http://svn.apache.org/viewcvs/incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java?rev=233031&view=auto
==============================================================================
--- incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java (added)
+++ incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java Tue Aug 16 11:33:34 2005
@@ -0,0 +1,1374 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   Licensed 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.osgi.bundle.bundlerepository;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.osgi.service.bundlerepository.BundleRecord;
+import org.apache.osgi.service.bundlerepository.BundleRepository;
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class ObrCommandImpl implements Command
+{
+    private static final String HELP_CMD = "help";
+    private static final String URLS_CMD = "urls";
+    private static final String LIST_CMD = "list";
+    private static final String INFO_CMD = "info";
+    private static final String DEPLOY_CMD = "deploy";
+//    private static final String INSTALL_CMD = "install";
+    private static final String START_CMD = "start";
+//    private static final String UPDATE_CMD = "update";
+    private static final String SOURCE_CMD = "source";
+
+    private static final String NODEPS_SWITCH = "-nodeps";
+    private static final String CHECK_SWITCH = "-check";
+    private static final String EXTRACT_SWITCH = "-x";
+
+    private BundleContext m_context = null;
+    private BundleRepository m_repo = null;
+
+    public ObrCommandImpl(BundleContext context, BundleRepository repo)
+    {
+        m_context = context;
+        m_repo = repo;
+    }
+
+    public String getName()
+    {
+        return "obr";
+    }
+
+    public String getUsage()
+    {
+        return "obr help";
+    }
+
+    public String getShortDescription()
+    {
+        return "OSGi bundle repository.";
+    }
+
+    public synchronized void execute(String commandLine, PrintStream out, PrintStream err)
+    {
+        try
+        {
+            // Parse the commandLine to get the OBR command.
+            StringTokenizer st = new StringTokenizer(commandLine);
+            // Ignore the invoking command.
+            st.nextToken();
+            // Try to get the OBR command, default is HELP command.
+            String command = HELP_CMD;
+            try
+            {
+                command = st.nextToken();
+            }
+            catch (Exception ex)
+            {
+                // Ignore.
+            }
+            
+            // Perform the specified command.
+            if ((command == null) || (command.equals(HELP_CMD)))
+            {
+                help(out, st);
+            }
+            else
+            {
+                if (command.equals(URLS_CMD))
+                {
+                    urls(commandLine, command, out, err);
+                }
+                else if (command.equals(LIST_CMD))
+                {
+                    list(commandLine, command, out, err);
+                }
+                else if (command.equals(INFO_CMD))
+                {
+                    info(commandLine, command, out, err);
+                }
+                else if (command.equals(DEPLOY_CMD) || command.equals(START_CMD))
+                {
+                    deploy(commandLine, command, out, err);
+                }
+/*
+                else if (command.equals(INSTALL_CMD) || command.equals(START_CMD))
+                {
+                    install(commandLine, command, out, err);
+                }
+                else if (command.equals(UPDATE_CMD))
+                {
+                    update(commandLine, command, out, err);
+                }
+*/
+                else if (command.equals(SOURCE_CMD))
+                {
+                    source(commandLine, command, out, err);
+                }
+                else
+                {
+                    err.println("Unknown command: " + command);
+                }
+            }
+        }
+        catch (InvalidSyntaxException ex)
+        {
+            err.println("Syntax error: " + ex.getMessage());
+        }
+        catch (IOException ex)
+        {
+            err.println("Error: " + ex);
+        }
+    }
+
+    private void urls(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException
+    {
+        // Parse the commandLine.
+        StringTokenizer st = new StringTokenizer(commandLine);
+        // Ignore the "obr" command.
+        st.nextToken();
+        // Ignore the "urls" command.
+        st.nextToken();
+
+        int count = st.countTokens();
+        String[] urls = new String[count];
+        for (int i = 0; i < count; i++)
+        {
+            urls[i] = st.nextToken();
+        }
+    
+        if (count > 0)
+        {
+            m_repo.setRepositoryURLs(urls);
+        }
+        else
+        {
+            urls = m_repo.getRepositoryURLs();
+            if (urls != null)
+            {
+                for (int i = 0; i < urls.length; i++)
+                {
+                    out.println(urls[i]);
+                }
+            }
+            else
+            {
+                out.println("No repository URLs are set.");
+            }
+        }
+    }
+
+    private void list(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException
+    {
+        // Create a stream tokenizer for the command line string,
+        // since the syntax for install/start is more sophisticated.
+        StringReader sr = new StringReader(commandLine);
+        StreamTokenizer tokenizer = new StreamTokenizer(sr);
+        tokenizer.resetSyntax();
+        tokenizer.quoteChar('\'');
+        tokenizer.quoteChar('\"');
+        tokenizer.whitespaceChars('\u0000', '\u0020');
+        tokenizer.wordChars('A', 'Z');
+        tokenizer.wordChars('a', 'z');
+        tokenizer.wordChars('0', '9');
+        tokenizer.wordChars('\u00A0', '\u00FF');
+        tokenizer.wordChars('.', '.');
+        tokenizer.wordChars('-', '-');
+        tokenizer.wordChars('_', '_');
+
+        // Ignore the invoking command name and the OBR command.
+        int type = tokenizer.nextToken();
+        type = tokenizer.nextToken();
+
+        String substr = null;
+    
+        for (type = tokenizer.nextToken();
+            type != StreamTokenizer.TT_EOF;
+            type = tokenizer.nextToken())
+        {
+            // Add a space in between tokens.
+            if (substr == null)
+            {
+                substr = "";
+            }
+            else
+            {
+                substr += " ";
+            }
+                        
+            if ((type == StreamTokenizer.TT_WORD) ||
+                (type == '\'') || (type == '"'))
+            {
+                substr += tokenizer.sval.toLowerCase();
+            }
+        }
+
+        boolean found = false;
+        BundleRecord[] records = m_repo.getBundleRecords();
+        for (int recIdx = 0; recIdx < records.length; recIdx++)
+        {
+            String name = (String)
+                records[recIdx].getAttribute(BundleRecord.BUNDLE_NAME);
+            String symName = (String)
+                records[recIdx].getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+            if ((substr == null) ||
+                ((name != null) && (name.toLowerCase().indexOf(substr) >= 0)) ||
+                ((symName != null) && (symName.toLowerCase().indexOf(substr) >= 0)))
+            {
+                found = true;
+                String version =
+                    (String) records[recIdx].getAttribute(BundleRecord.BUNDLE_VERSION);
+                if (version != null)
+                {
+                    out.println(name + " (" + version + ")");
+                }
+                else
+                {
+                    out.println(name);
+                }
+            }
+        }
+    
+        if (!found)
+        {
+            out.println("No matching bundles.");
+        }
+    }
+
+    private void info(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        ParsedCommand pc = parseInfo(commandLine);
+        for (int i = 0; (pc != null) && (i < pc.getTargetCount()); i++)                
+        {
+            BundleRecord[] records = searchRepository(
+                pc.getTargetId(i), pc.getTargetVersion(i));
+            if (records == null)
+            {
+                err.println("Unknown bundle and/or version: "
+                    + pc.getTargetId(i));
+            }
+            else if (records.length > 1)
+            {
+                err.println("More than one version exists: "
+                    + pc.getTargetId(i));
+            }
+            else
+            {
+                records[0].printAttributes(out);
+            }
+        }
+    }
+
+    private void deploy(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        ParsedCommand pc = parseInstallStart(commandLine);
+        _deploy(pc, command, out, err);
+    }
+
+    private void _deploy(
+        ParsedCommand pc, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        for (int i = 0; (pc != null) && (i < pc.getTargetCount()); i++)                
+        {
+            // Find the target's bundle record.
+            BundleRecord record = selectNewestVersion(
+                searchRepository(pc.getTargetId(i), pc.getTargetVersion(i)));
+            if (record != null)
+            {
+                m_repo.deployBundle(
+                    out, // Output stream.
+                    err, // Error stream.
+                    (String) record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME),
+                    Util.parseVersionString((String)record.getAttribute(BundleRecord.BUNDLE_VERSION)),
+                    pc.isResolve(), // Resolve dependencies.
+                    command.equals(START_CMD)); // Start.
+            }
+            else
+            {
+                err.println("Unknown bundle or amiguous version: "
+                    + pc.getTargetId(i));
+            }
+        }
+    }
+/*
+    private void install(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        // Parse the command line to get all local targets to install.
+        ParsedCommand pc = parseInstallStart(commandLine);
+        
+        // Loop through each local target and try to find
+        // the corresponding bundle record from the repository.
+        for (int targetIdx = 0;
+            (pc != null) && (targetIdx < pc.getTargetCount());
+            targetIdx++)                
+        {
+            // Get the current target's name and version.
+            String targetName = pc.getTargetId(targetIdx);
+            String targetVersionString = pc.getTargetVersion(targetIdx);
+
+            // Make sure the bundle is not already installed.
+            Bundle bundle = findLocalBundle(targetName, targetVersionString);
+            if (bundle == null)
+            {
+                _deploy(pc, command, out, err);
+            }
+            else
+            {
+                err.println("Already installed: " + targetName);
+            }
+        }
+    }
+
+    private void update(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        // Parse the command line to get all local targets to update.
+        ParsedCommand pc = parseUpdate(commandLine);
+
+        if (pc.isCheck())        
+        {
+            updateCheck(out, err);
+        }
+        else
+        {
+            // Loop through each local target and try to find
+            // the corresponding bundle record from the repository.
+            for (int targetIdx = 0;
+                (pc != null) && (targetIdx < pc.getTargetCount());
+                targetIdx++)                
+            {
+                // Get the current target's name and version.
+                String targetName = pc.getTargetId(targetIdx);
+                String targetVersionString = pc.getTargetVersion(targetIdx);
+
+                // Make sure the bundle is not already installed.
+                Bundle bundle = findLocalBundle(targetName, targetVersionString);
+                if (bundle != null)
+                {
+                    _deploy(pc, command, out, err);
+                }
+                else
+                {
+                    err.println("Not installed: " + targetName);
+                }
+            }
+        }
+    }
+
+    private void updateCheck(PrintStream out, PrintStream err)
+        throws IOException
+    {
+        Bundle[] bundles = m_context.getBundles();
+
+        // Loop through each local target and try to find
+        // the corresponding locally installed bundle.
+        for (int bundleIdx = 0;
+            (bundles != null) && (bundleIdx < bundles.length);
+            bundleIdx++)
+        {
+            // Ignore the system bundle.
+            if (bundles[bundleIdx].getBundleId() == 0)
+            {
+                continue;
+            }
+
+            // Get the local bundle's update location.
+            String localLoc = (String)
+                bundles[bundleIdx].getHeaders().get(Constants.BUNDLE_UPDATELOCATION);
+            if (localLoc == null)
+            {
+                // Without an update location, there is no way to
+                // check for an update, so ignore the bundle.
+                continue;
+            }
+
+            // Get the local bundle's version.
+            String localVersion = (String)
+                bundles[bundleIdx].getHeaders().get(Constants.BUNDLE_VERSION);
+            localVersion = (localVersion == null) ? "0.0.0" : localVersion;
+
+            // Get the matching repository bundle records.
+            BundleRecord[] records = m_repo.getBundleRecords(
+                (String) bundles[bundleIdx].getHeaders().get(Constants.BUNDLE_NAME));
+
+            // Loop through all records to see if there is an update.
+            for (int recordIdx = 0;
+                (records != null) && (recordIdx < records.length);
+                recordIdx++)
+            {
+                String remoteLoc = (String)
+                    records[recordIdx].getAttribute(BundleRecord.BUNDLE_UPDATELOCATION);
+                if (remoteLoc == null)
+                {
+                    continue;
+                }
+
+                // If the update locations are equal, then compare versions.
+                if (remoteLoc.equals(localLoc))
+                {
+                    String remoteVersion = (String)
+                        records[recordIdx].getAttribute(BundleRecord.BUNDLE_VERSION);
+                    if (remoteVersion != null)
+                    {
+                        int result = Util.compareVersion(
+                            Util.parseVersionString(remoteVersion),
+                            Util.parseVersionString(localVersion));
+                        if (result > 0)
+                        {
+                            out.println(
+                                records[recordIdx].getAttribute(BundleRecord.BUNDLE_NAME)
+                                + " update available.");
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+*/
+    private void source(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        // Parse the command line to get all local targets to update.
+        ParsedCommand pc = parseSource(commandLine);
+        
+        for (int i = 0; i < pc.getTargetCount(); i++)
+        {
+            BundleRecord[] records =
+                searchRepository(pc.getTargetId(i), pc.getTargetVersion(i));
+            if (records == null)
+            {
+                err.println("Unknown bundle and/or version: "
+                    + pc.getTargetId(i));
+            }
+            else if (records.length > 1)
+            {
+                err.println("More than one version exists: "
+                    + pc.getTargetId(i));
+            }
+            else
+            {
+                String srcURL = (String)
+                records[0].getAttribute(BundleRecord.BUNDLE_SOURCEURL);
+                if (srcURL != null)
+                {
+                    FileUtil.downloadSource(
+                        out, err, srcURL, pc.getDirectory(), pc.isExtract());
+                }
+                else
+                {
+                    err.println("Missing source URL: " + pc.getTargetId(i));
+                }
+            }
+        }
+    }
+
+    private BundleRecord[] searchRepository(String targetId, String targetVersion)
+    {
+        // The targetId may be a bundle name or a bundle symbolic name.
+        // Query for symbolic name first, since it is more specific. If
+        // that can't be found, then compare bundle names.
+        BundleRecord[] records = null;
+        if (targetVersion != null)
+        {
+            BundleRecord record = m_repo.getBundleRecord(
+                targetId, Util.parseVersionString(targetVersion));
+            if (record != null)
+            {
+                records = new BundleRecord[] { record };
+            }
+        }
+        else
+        {
+            records = m_repo.getBundleRecords(targetId);
+        }
+
+        if (records == null)
+        {
+            List recordList = new ArrayList();
+            records = m_repo.getBundleRecords();
+            for (int i = 0; (records != null) && (i < records.length); i++)
+            {
+                if (targetId.compareToIgnoreCase((String)
+                    records[i].getAttribute(BundleRecord.BUNDLE_NAME)) == 0)
+                {
+                    int[] v1 = Util.parseVersionString(targetVersion);
+                    int[] v2 = Util.parseVersionString((String)
+                        records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+                    if ((targetVersion == null) ||
+                        ((targetVersion != null) && (Util.compareVersion(v1, v2) == 0)))
+                    {
+                        recordList.add(records[i]);
+                    }
+                }
+            }
+            records = (recordList.size() == 0)
+                ? null
+                : (BundleRecord[]) recordList.toArray(new BundleRecord[recordList.size()]);
+        }
+
+        return records;
+    }
+
+    public BundleRecord selectNewestVersion(BundleRecord[] records)
+    {
+        int idx = -1;
+        int[] v = null;
+        for (int i = 0; (records != null) && (i < records.length); i++)
+        {
+            if (i == 0)
+            {
+                idx = 0;
+                v = Util.parseVersionString((String)
+                    records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+            }
+            else
+            {
+                int[] vtmp = Util.parseVersionString((String)
+                    records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+                if (Util.compareVersion(vtmp, v) > 0)
+                {
+                    idx = i;
+                    v = vtmp;
+                }
+            }
+        }
+
+        return (idx < 0) ? null : records[idx];
+    }
+
+    private Bundle findLocalBundle(String name, String versionString)
+    {
+        Bundle bundle = null;
+
+        // Get the name only if there is no version, but error
+        // if there are multiple matches for the same name.
+        if (versionString == null)
+        {
+            // Perhaps the target name is a bundle ID and
+            // not a name, so try to interpret as a long.
+            try
+            {
+                bundle = m_context.getBundle(Long.parseLong(name));
+            }
+            catch (NumberFormatException ex)
+            {
+                // The bundle is not a number, so look for a local
+                // bundle with the same name.
+                Bundle[] matchingBundles = findLocalBundlesBySymbolicName(name);
+
+                // If only one matches, then select is.
+                if (matchingBundles.length == 1)
+                {
+                    bundle = matchingBundles[0];
+                }
+            }
+        }
+        else
+        {
+            // Find the local bundle by name and version.
+            bundle = findLocalBundleByVersion(
+                name, Util.parseVersionString(versionString));
+        }
+
+        return bundle;
+    }
+
+    private Bundle findLocalBundleByVersion(String symName, int[] version)
+    {
+        // Get bundles with matching name.
+        Bundle[] targets = findLocalBundlesBySymbolicName(symName);
+
+        // Find bundle with matching version.
+        if (targets.length > 0)
+        {
+            for (int i = 0; i < targets.length; i++)
+            {
+                String targetName = (String)
+                    targets[i].getHeaders().get(BundleRecord.BUNDLE_SYMBOLICNAME);
+                int[] targetVersion = Util.parseVersionString((String)
+                    targets[i].getHeaders().get(BundleRecord.BUNDLE_VERSION));
+            
+                if ((targetName != null) &&
+                    targetName.equalsIgnoreCase(symName) &&
+                    (Util.compareVersion(targetVersion, version) == 0))
+                {
+                    return targets[i];
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private Bundle[] findLocalBundlesBySymbolicName(String symName)
+    {
+        // Get local bundles.
+        Bundle[] bundles = m_context.getBundles();
+
+        // Find bundles with matching name.
+        Bundle[] targets = new Bundle[0];
+        for (int i = 0; i < bundles.length; i++)
+        {
+            String targetName = (String)
+                bundles[i].getHeaders().get(BundleRecord.BUNDLE_SYMBOLICNAME);
+            if (targetName == null)
+            {
+                targetName = bundles[i].getLocation();
+            }
+            if ((targetName != null) && targetName.equalsIgnoreCase(symName))
+            {
+                Bundle[] newTargets = new Bundle[targets.length + 1];
+                System.arraycopy(targets, 0, newTargets, 0, targets.length);
+                newTargets[targets.length] = bundles[i];
+                targets = newTargets;
+            }
+        }
+
+        return targets;
+    }
+
+    private ParsedCommand parseInfo(String commandLine)
+        throws IOException, InvalidSyntaxException
+    {
+        // Create a stream tokenizer for the command line string,
+        // since the syntax for install/start is more sophisticated.
+        StringReader sr = new StringReader(commandLine);
+        StreamTokenizer tokenizer = new StreamTokenizer(sr);
+        tokenizer.resetSyntax();
+        tokenizer.quoteChar('\'');
+        tokenizer.quoteChar('\"');
+        tokenizer.whitespaceChars('\u0000', '\u0020');
+        tokenizer.wordChars('A', 'Z');
+        tokenizer.wordChars('a', 'z');
+        tokenizer.wordChars('0', '9');
+        tokenizer.wordChars('\u00A0', '\u00FF');
+        tokenizer.wordChars('.', '.');
+        tokenizer.wordChars('-', '-');
+        tokenizer.wordChars('_', '_');
+    
+        // Ignore the invoking command name and the OBR command.
+        int type = tokenizer.nextToken();
+        type = tokenizer.nextToken();
+    
+        int EOF = 1;
+        int SWITCH = 2;
+        int TARGET = 4;
+        int VERSION = 8;
+        int VERSION_VALUE = 16;
+
+        // Construct an install record.
+        ParsedCommand pc = new ParsedCommand();
+        String currentTargetName = null;
+
+        // The state machine starts by expecting either a
+        // SWITCH or a TARGET.
+        int expecting = (TARGET);
+        while (true)
+        {
+            // Get the next token type.
+            type = tokenizer.nextToken();
+            switch (type)
+            {
+                // EOF received.
+                case StreamTokenizer.TT_EOF:
+                    // Error if we weren't expecting EOF.
+                    if ((expecting & EOF) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Expecting more arguments.", null);
+                    }
+                    // Add current target if there is one.
+                    if (currentTargetName != null)
+                    {
+                        pc.addTarget(currentTargetName, null);
+                    }
+                    // Return cleanly.
+                    return pc;
+
+                // WORD or quoted WORD received.
+                case StreamTokenizer.TT_WORD:
+                case '\'':
+                case '\"':
+                    // If we are expecting a target, the record it.
+                    if ((expecting & TARGET) > 0)
+                    {
+                        // Add current target if there is one.
+                        if (currentTargetName != null)
+                        {
+                            pc.addTarget(currentTargetName, null);
+                        }
+                        // Set the new target as the current target.
+                        currentTargetName = tokenizer.sval;
+                        expecting = (EOF | TARGET | VERSION);
+                    }
+                    else if ((expecting & VERSION_VALUE) > 0)
+                    {
+                        pc.addTarget(currentTargetName, tokenizer.sval);
+                        currentTargetName = null;
+                        expecting = (EOF | TARGET);
+                    }
+                    else
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting '" + tokenizer.sval + "'.", null);
+                    }
+                    break;
+
+                // Version separator character received.
+                case ';':
+                    // Error if we weren't expecting the version separator.
+                    if ((expecting & VERSION) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting version.", null);
+                    }
+                    // Otherwise, we will only expect a version value next.
+                    expecting = (VERSION_VALUE);
+                    break;
+            }
+        }
+    }
+
+    private ParsedCommand parseInstallStart(String commandLine)
+        throws IOException, InvalidSyntaxException
+    {
+        // Create a stream tokenizer for the command line string,
+        // since the syntax for install/start is more sophisticated.
+        StringReader sr = new StringReader(commandLine);
+        StreamTokenizer tokenizer = new StreamTokenizer(sr);
+        tokenizer.resetSyntax();
+        tokenizer.quoteChar('\'');
+        tokenizer.quoteChar('\"');
+        tokenizer.whitespaceChars('\u0000', '\u0020');
+        tokenizer.wordChars('A', 'Z');
+        tokenizer.wordChars('a', 'z');
+        tokenizer.wordChars('0', '9');
+        tokenizer.wordChars('\u00A0', '\u00FF');
+        tokenizer.wordChars('.', '.');
+        tokenizer.wordChars('-', '-');
+        tokenizer.wordChars('_', '_');
+    
+        // Ignore the invoking command name and the OBR command.
+        int type = tokenizer.nextToken();
+        type = tokenizer.nextToken();
+    
+        int EOF = 1;
+        int SWITCH = 2;
+        int TARGET = 4;
+        int VERSION = 8;
+        int VERSION_VALUE = 16;
+
+        // Construct an install record.
+        ParsedCommand pc = new ParsedCommand();
+        String currentTargetName = null;
+
+        // The state machine starts by expecting either a
+        // SWITCH or a TARGET.
+        int expecting = (SWITCH | TARGET);
+        while (true)
+        {
+            // Get the next token type.
+            type = tokenizer.nextToken();
+            switch (type)
+            {
+                // EOF received.
+                case StreamTokenizer.TT_EOF:
+                    // Error if we weren't expecting EOF.
+                    if ((expecting & EOF) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Expecting more arguments.", null);
+                    }
+                    // Add current target if there is one.
+                    if (currentTargetName != null)
+                    {
+                        pc.addTarget(currentTargetName, null);
+                    }
+                    // Return cleanly.
+                    return pc;
+
+                // WORD or quoted WORD received.
+                case StreamTokenizer.TT_WORD:
+                case '\'':
+                case '\"':
+                    // If we are expecting a command SWITCH and the token
+                    // equals a command SWITCH, then record it.
+                    if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(NODEPS_SWITCH))
+                    {
+                        pc.setResolve(false);
+                        expecting = (EOF | TARGET);
+                    }
+                    // If we are expecting a target, the record it.
+                    else if ((expecting & TARGET) > 0)
+                    {
+                        // Add current target if there is one.
+                        if (currentTargetName != null)
+                        {
+                            pc.addTarget(currentTargetName, null);
+                        }
+                        // Set the new target as the current target.
+                        currentTargetName = tokenizer.sval;
+                        expecting = (EOF | TARGET | VERSION);
+                    }
+                    else if ((expecting & VERSION_VALUE) > 0)
+                    {
+                        pc.addTarget(currentTargetName, tokenizer.sval);
+                        currentTargetName = null;
+                        expecting = (EOF | TARGET);
+                    }
+                    else
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting '" + tokenizer.sval + "'.", null);
+                    }
+                    break;
+
+                // Version separator character received.
+                case ';':
+                    // Error if we weren't expecting the version separator.
+                    if ((expecting & VERSION) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting version.", null);
+                    }
+                    // Otherwise, we will only expect a version value next.
+                    expecting = (VERSION_VALUE);
+                    break;
+            }
+        }
+    }
+
+    private ParsedCommand parseUpdate(String commandLine)
+        throws IOException, InvalidSyntaxException
+    {
+        // Create a stream tokenizer for the command line string,
+        // since the syntax for install/start is more sophisticated.
+        StringReader sr = new StringReader(commandLine);
+        StreamTokenizer tokenizer = new StreamTokenizer(sr);
+        tokenizer.resetSyntax();
+        tokenizer.quoteChar('\'');
+        tokenizer.quoteChar('\"');
+        tokenizer.whitespaceChars('\u0000', '\u0020');
+        tokenizer.wordChars('A', 'Z');
+        tokenizer.wordChars('a', 'z');
+        tokenizer.wordChars('0', '9');
+        tokenizer.wordChars('\u00A0', '\u00FF');
+        tokenizer.wordChars('.', '.');
+        tokenizer.wordChars('-', '-');
+        tokenizer.wordChars('_', '_');
+    
+        // Ignore the invoking command name and the OBR command.
+        int type = tokenizer.nextToken();
+        type = tokenizer.nextToken();
+    
+        int EOF = 1;
+        int SWITCH = 2;
+        int TARGET = 4;
+        int VERSION = 8;
+        int VERSION_VALUE = 16;
+
+        // Construct an install record.
+        ParsedCommand pc = new ParsedCommand();
+        String currentTargetName = null;
+
+        // The state machine starts by expecting either a
+        // SWITCH or a TARGET.
+        int expecting = (SWITCH | TARGET);
+        while (true)
+        {
+            // Get the next token type.
+            type = tokenizer.nextToken();
+            switch (type)
+            {
+                // EOF received.
+                case StreamTokenizer.TT_EOF:
+                    // Error if we weren't expecting EOF.
+                    if ((expecting & EOF) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Expecting more arguments.", null);
+                    }
+                    // Add current target if there is one.
+                    if (currentTargetName != null)
+                    {
+                        pc.addTarget(currentTargetName, null);
+                    }
+                    // Return cleanly.
+                    return pc;
+
+                // WORD or quoted WORD received.
+                case StreamTokenizer.TT_WORD:
+                case '\'':
+                case '\"':
+                    // If we are expecting a command SWITCH and the token
+                    // equals a NODEPS switch, then record it.
+                    if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(NODEPS_SWITCH))
+                    {
+                        pc.setResolve(false);
+                        expecting = (EOF | TARGET);
+                    }
+                    // If we are expecting a command SWITCH and the token
+                    // equals a CHECK swithc, then record it.
+                    else if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(CHECK_SWITCH))
+                    {
+                        pc.setCheck(true);
+                        expecting = (EOF);
+                    }
+                    // If we are expecting a target, the record it.
+                    else if ((expecting & TARGET) > 0)
+                    {
+                        // Add current target if there is one.
+                        if (currentTargetName != null)
+                        {
+                            pc.addTarget(currentTargetName, null);
+                        }
+                        // Set the new target as the current target.
+                        currentTargetName = tokenizer.sval;
+                        expecting = (EOF | TARGET | VERSION);
+                    }
+                    else if ((expecting & VERSION_VALUE) > 0)
+                    {
+                        pc.addTarget(currentTargetName, tokenizer.sval);
+                        currentTargetName = null;
+                        expecting = (EOF | TARGET);
+                    }
+                    else
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting '" + tokenizer.sval + "'.", null);
+                    }
+                    break;
+
+                // Version separator character received.
+                case ';':
+                    // Error if we weren't expecting the version separator.
+                    if ((expecting & VERSION) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting version.", null);
+                    }
+                    // Otherwise, we will only expect a version value next.
+                    expecting = (VERSION_VALUE);
+                    break;
+            }
+        }
+    }
+
+    private ParsedCommand parseSource(String commandLine)
+        throws IOException, InvalidSyntaxException
+    {
+        // Create a stream tokenizer for the command line string,
+        // since the syntax for install/start is more sophisticated.
+        StringReader sr = new StringReader(commandLine);
+        StreamTokenizer tokenizer = new StreamTokenizer(sr);
+        tokenizer.resetSyntax();
+        tokenizer.quoteChar('\'');
+        tokenizer.quoteChar('\"');
+        tokenizer.whitespaceChars('\u0000', '\u0020');
+        tokenizer.wordChars('A', 'Z');
+        tokenizer.wordChars('a', 'z');
+        tokenizer.wordChars('0', '9');
+        tokenizer.wordChars('\u00A0', '\u00FF');
+        tokenizer.wordChars('.', '.');
+        tokenizer.wordChars('-', '-');
+        tokenizer.wordChars('_', '_');
+        tokenizer.wordChars('/', '/');
+    
+        // Ignore the invoking command name and the OBR command.
+        int type = tokenizer.nextToken();
+        type = tokenizer.nextToken();
+    
+        int EOF = 1;
+        int SWITCH = 2;
+        int DIRECTORY = 4;
+        int TARGET = 8;
+        int VERSION = 16;
+        int VERSION_VALUE = 32;
+
+        // Construct an install record.
+        ParsedCommand pc = new ParsedCommand();
+        String currentTargetName = null;
+
+        // The state machine starts by expecting either a
+        // SWITCH or a DIRECTORY.
+        int expecting = (SWITCH | DIRECTORY);
+        while (true)
+        {
+            // Get the next token type.
+            type = tokenizer.nextToken();
+            switch (type)
+            {
+                // EOF received.
+                case StreamTokenizer.TT_EOF:
+                    // Error if we weren't expecting EOF.
+                    if ((expecting & EOF) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Expecting more arguments.", null);
+                    }
+                    // Add current target if there is one.
+                    if (currentTargetName != null)
+                    {
+                        pc.addTarget(currentTargetName, null);
+                    }
+                    // Return cleanly.
+                    return pc;
+
+                // WORD or quoted WORD received.
+                case StreamTokenizer.TT_WORD:
+                case '\'':
+                case '\"':
+                    // If we are expecting a command SWITCH and the token
+                    // equals a command SWITCH, then record it.
+                    if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(EXTRACT_SWITCH))
+                    {
+                        pc.setExtract(true);
+                        expecting = (DIRECTORY);
+                    }
+                    // If we are expecting a directory, the record it.
+                    else if ((expecting & DIRECTORY) > 0)
+                    {
+                        // Set the directory for the command.
+                        pc.setDirectory(tokenizer.sval);
+                        expecting = (TARGET);
+                    }
+                    // If we are expecting a target, the record it.
+                    else if ((expecting & TARGET) > 0)
+                    {
+                        // Add current target if there is one.
+                        if (currentTargetName != null)
+                        {
+                            pc.addTarget(currentTargetName, null);
+                        }
+                        // Set the new target as the current target.
+                        currentTargetName = tokenizer.sval;
+                        expecting = (EOF | TARGET | VERSION);
+                    }
+                    else if ((expecting & VERSION_VALUE) > 0)
+                    {
+                        pc.addTarget(currentTargetName, tokenizer.sval);
+                        currentTargetName = null;
+                        expecting = (EOF | TARGET);
+                    }
+                    else
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting '" + tokenizer.sval + "'.", null);
+                    }
+                    break;
+
+                // Version separator character received.
+                case ';':
+                    // Error if we weren't expecting the version separator.
+                    if ((expecting & VERSION) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting version.", null);
+                    }
+                    // Otherwise, we will only expect a version value next.
+                    expecting = (VERSION_VALUE);
+                    break;
+            }
+        }
+    }
+
+    private void help(PrintStream out, StringTokenizer st)
+    {
+        String command = HELP_CMD;
+        if (st.hasMoreTokens())
+        {
+            command = st.nextToken();
+        }
+        if (command.equals(URLS_CMD))
+        {
+            out.println("");
+            out.println("obr " + URLS_CMD + " [<repository-file-url> ...]");
+            out.println("");
+            out.println(
+                "This command gets or sets the URLs to the repository files\n" +                "used by OBR. Specify no arguments to get the current repository\n" + 
+                "URLs or specify a space-delimited list of URLs to change the\n" +
+                "URLs. Each URL should point to a file containing meta-data about\n" +                "available bundles in XML format.");
+            out.println("");
+        }
+        else if (command.equals(LIST_CMD))
+        {
+            out.println("");
+            out.println("obr " + LIST_CMD + " [<string> ...]");
+            out.println("");
+            out.println(
+                "This command lists bundles available in the bundle repository.\n" +
+                "If no arguments are specified, then all available bundles are\n" +
+                "listed, otherwise any arguments are concatenated with spaces\n" +
+                "and used as a substring filter on the bundle names.");
+            out.println("");
+        }
+        else if (command.equals(INFO_CMD))
+        {
+            out.println("");
+            out.println("obr " + INFO_CMD
+                + " <bundle-name>[;<version>] ...");
+            out.println("");
+            out.println(
+                "This command displays the meta-data for the specified bundles.\n" +
+                "If a bundle's name contains spaces, then it must be surrounded\n" +
+                "by quotes. It is also possible to specify a precise version\n" +
+                "if more than one version exists, such as:\n" +
+                "\n" +
+                "    obr info \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "The above example retrieves the meta-data for version \"1.0.0\"\n" +
+                "of the bundle named \"Bundle Repository\".");
+            out.println("");
+        }
+        else if (command.equals(DEPLOY_CMD))
+        {
+            out.println("");
+            out.println("obr " + DEPLOY_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+            out.println("");
+            out.println(
+                "This command tries to install or update the specified bundles\n" +
+                "and all of their dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch\n" +
+                "to ignore dependencies. You can specify either the bundle name or\n" +
+                "the bundle identifier. If a bundle's name contains spaces, then\n" +
+                "it must be surrounded by quotes. It is also possible to specify a\n" +                "precise version if more than one version exists, such as:\n" +
+                "\n" +
+                "    obr deploy \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "For the above example, if version \"1.0.0\" of \"Bundle Repository\" is\n" +
+                "already installed locally, then the command will attempt to update it\n" +
+                "and all of its dependencies; otherwise, the command will install it\n" +
+                "and all of its dependencies.");
+            out.println("");
+        }
+/*
+        else if (command.equals(INSTALL_CMD))
+        {
+            out.println("");
+            out.println("obr " + INSTALL_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ...");
+            out.println("");
+            out.println(
+                "This command installs the specified bundles and all of their\n" +
+                "dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch to ignore\n" +
+                "dependencies. If a bundle's name contains spaces, then it\n" +
+                "must be surrounded by quotes. If a specified bundle is already\n" +                "installed, then this command has no effect. It is also possible\n" +                "to specify a precise version if more than one version exists,\n" +                "such as:\n" +
+                "\n" +
+                "    obr install \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "The above example installs version \"1.0.0\" of the bundle\n" +
+                "named \"Bundle Repository\" and its dependencies. ");
+            out.println("");
+        }
+*/
+        else if (command.equals(START_CMD))
+        {
+            out.println("");
+            out.println("obr " + START_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ...");
+            out.println("");
+            out.println(
+                "This command installs and starts the specified bundles and all\n" +
+                "of their dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch to\n" +
+                "ignore dependencies. If a bundle's name contains spaces, then\n" +
+                "it must be surrounded by quotes. If a specified bundle is already\n" +                "installed, then this command has no effect. It is also possible\n" +                "to specify a precise version if more than one version exists,\n" +                "such as:\n" +
+                "\n" +
+                "    obr start \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "The above example installs and starts version \"1.0.0\" of the\n" +
+                "bundle named \"Bundle Repository\" and its dependencies.");
+            out.println("");
+        }
+/*
+        else if (command.equals(UPDATE_CMD))
+        {
+            out.println("");
+            out.println("obr " + UPDATE_CMD + " " + CHECK_SWITCH);
+            out.println("");
+            out.println("obr " + UPDATE_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+            out.println("");
+            out.println(
+                "The first form of the command above checks for available updates\n" +                "and the second updates the specified locally installed bundles\n" +
+                "and all of their dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch\n" +
+                "to ignore dependencies. You can specify either the bundle name or\n" +
+                "the bundle identifier. If a bundle's name contains spaces, then\n" +
+                "it must be surrounded by quotes. If a specified bundle is not\n" +                "already installed, then this command has no effect. It is also\n" +                "possible to specify a precise version if more than one version\n" +                "exists, such as:\n" +
+                "\n" +
+                "    obr update \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "The above example updates version \"1.0.0\" of the bundle named\n" +
+                "\"Bundle Repository\" and its dependencies. The update command may\n" +
+                "install new bundles if the updated bundles have new dependencies.");
+            out.println("");
+        }
+*/
+        else if (command.equals(SOURCE_CMD))
+        {
+            out.println("");
+            out.println("obr " + SOURCE_CMD
+                + " [" + EXTRACT_SWITCH
+                + "] <local-dir> <bundle-name>[;<version>] ...");
+            out.println("");
+            out.println(
+                "This command retrieves the source archives of the specified\n" +
+                "bundles and saves them to the specified local directory; use\n" +
+                "the \"" + EXTRACT_SWITCH + "\" switch to automatically extract the source archives.\n" +
+                "If a bundle name contains spaces, then it must be surrounded\n" +
+                "by quotes. It is also possible to specify a precise version if\n" +                "more than one version exists, such as:\n" +
+                "\n" +
+                "    obr source /home/rickhall/tmp \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "The above example retrieves the source archive of version \"1.0.0\"\n" +
+                "of the bundle named \"Bundle Repository\" and saves it to the\n" +
+                "specified local directory.");
+            out.println("");
+        }
+        else
+        {
+            out.println("obr " + HELP_CMD
+                + " [" + URLS_CMD + " | " + LIST_CMD
+//                + " | " + INFO_CMD + " | " + INSTALL_CMD
+                + " | " + INFO_CMD
+                + " | " + DEPLOY_CMD + " | " + START_CMD
+//                + " | " + UPDATE_CMD + " | " + SOURCE_CMD + "]");
+                + " | " + SOURCE_CMD + "]");
+            out.println("obr " + URLS_CMD + " [<repository-file-url> ...]");
+            out.println("obr " + LIST_CMD + " [<string> ...]");
+            out.println("obr " + INFO_CMD
+                + " <bundle-name>[;<version>] ...");
+            out.println("obr " + DEPLOY_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+//            out.println("obr " + INSTALL_CMD
+//                + " [" + NODEPS_SWITCH
+//                + "] <bundle-name>[;<version>] ...");
+            out.println("obr " + START_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ...");
+//            out.println("obr " + UPDATE_CMD + " " + CHECK_SWITCH);
+//            out.println("obr " + UPDATE_CMD
+//                + " [" + NODEPS_SWITCH
+//                + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+            out.println("obr " + SOURCE_CMD
+                + " [" + EXTRACT_SWITCH
+                + "] <local-dir> <bundle-name>[;<version>] ...");
+        }
+    }
+
+    private static class ParsedCommand
+    {
+        private static final int NAME_IDX = 0;
+        private static final int VERSION_IDX = 1;
+
+        private boolean m_isResolve = true;
+        private boolean m_isCheck = false;
+        private boolean m_isExtract = false;
+        private String m_dir = null;
+        private String[][] m_targets = new String[0][];
+        
+        public boolean isResolve()
+        {
+            return m_isResolve;
+        }
+        
+        public void setResolve(boolean b)
+        {
+            m_isResolve = b;
+        }
+
+        public boolean isCheck()
+        {
+            return m_isCheck;
+        }
+        
+        public void setCheck(boolean b)
+        {
+            m_isCheck = b;
+        }
+
+        public boolean isExtract()
+        {
+            return m_isExtract;
+        }
+        
+        public void setExtract(boolean b)
+        {
+            m_isExtract = b;
+        }
+
+        public String getDirectory()
+        {
+            return m_dir;
+        }
+        
+        public void setDirectory(String s)
+        {
+            m_dir = s;
+        }
+
+        public int getTargetCount()
+        {
+            return m_targets.length;
+        }
+        
+        public String getTargetId(int i)
+        {
+            if ((i < 0) || (i >= getTargetCount()))
+            {
+                return null;
+            }
+            return m_targets[i][NAME_IDX];
+        }
+        
+        public String getTargetVersion(int i)
+        {
+            if ((i < 0) || (i >= getTargetCount()))
+            {
+                return null;
+            }
+            return m_targets[i][VERSION_IDX];
+        }
+
+        public void addTarget(String name, String version)
+        {
+            String[][] newTargets = new String[m_targets.length + 1][];
+            System.arraycopy(m_targets, 0, newTargets, 0, m_targets.length);
+            newTargets[m_targets.length] = new String[] { name, version };
+            m_targets = newTargets;
+        }
+    }
+}

Added: incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java
URL: http://svn.apache.org/viewcvs/incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java?rev=233031&view=auto
==============================================================================
--- incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java (added)
+++ incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java Tue Aug 16 11:33:34 2005
@@ -0,0 +1,57 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   Licensed 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.osgi.bundle.bundlerepository;
+
+import org.apache.osgi.service.bundlerepository.IAttribute;
+
+public class R4Attribute implements IAttribute
+{
+    private String m_name = "";
+    private String m_value = "";
+    private boolean m_isMandatory = false;
+    
+    public R4Attribute(String name, String value, boolean isMandatory)
+    {
+        m_name = name;
+        m_value = value;
+        m_isMandatory = isMandatory;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Attribute#getName()
+    **/
+    public String getName()
+    {
+        return m_name;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Attribute#getValue()
+    **/
+    public String getValue()
+    {
+        return m_value;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Attribute#isMandatory()
+    **/
+    public boolean isMandatory()
+    {
+        return m_isMandatory;
+    }
+}
\ No newline at end of file

Added: incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java
URL: http://svn.apache.org/viewcvs/incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java?rev=233031&view=auto
==============================================================================
--- incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java (added)
+++ incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java Tue Aug 16 11:33:34 2005
@@ -0,0 +1,47 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   Licensed 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.osgi.bundle.bundlerepository;
+
+import org.apache.osgi.service.bundlerepository.IDirective;
+
+public class R4Directive implements IDirective
+{
+    private String m_name = "";
+    private String m_value = "";
+    
+    public R4Directive(String name, String value)
+    {
+        m_name = name;
+        m_value = value;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Directive#getName()
+    **/
+    public String getName()
+    {
+        return m_name;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Directive#getValue()
+    **/
+    public String getValue()
+    {
+        return m_value;
+    }
+}
\ No newline at end of file

Added: incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Package.java
URL: http://svn.apache.org/viewcvs/incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Package.java?rev=233031&view=auto
==============================================================================
--- incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Package.java (added)
+++ incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Package.java Tue Aug 16 11:33:34 2005
@@ -0,0 +1,501 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   Licensed 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.osgi.bundle.bundlerepository;
+
+import java.util.*;
+
+import org.apache.osgi.service.bundlerepository.*;
+import org.osgi.framework.Constants;
+
+//
+// This class is essentially the same as the R4Package class in Felix,
+// except that I had to add the parseDelimitedString() method. These
+// two classes should be unified.
+//
+
+/**
+ * This is a simple class to encapsulate a package declaration for
+ * bundle imports and exports for the bundle repository.
+**/
+public class R4Package implements IPackage
+{
+    private String m_id = "";
+    private IDirective[] m_directives = null;
+    private IAttribute[] m_attrs = null;
+    private IVersion m_versionLow = null;
+    private IVersion m_versionHigh = null;
+    private boolean m_isOptional = false;
+
+    protected R4Package(R4Package pkg)
+    {
+        m_id = pkg.m_id;
+        m_directives = pkg.m_directives;
+        m_attrs = pkg.m_attrs;
+        m_versionLow = pkg.m_versionLow;
+        m_versionHigh = pkg.m_versionHigh;
+        m_isOptional = pkg.m_isOptional;
+    }
+
+    public R4Package(String id, IDirective[] directives, IAttribute[] attrs)
+    {
+        m_id = id;
+        m_directives = (directives == null) ? new IDirective[0] : directives;
+        m_attrs = (attrs == null) ? new IAttribute[0] : attrs;
+
+        // Find mandatory and resolution directives, if present.
+        String mandatory = "";
+        for (int i = 0; i < m_directives.length; i++)
+        {
+            if (m_directives[i].getName().equals(Constants.MANDATORY_DIRECTIVE))
+            {
+                mandatory = m_directives[i].getValue();
+            }
+            else if (m_directives[i].getName().equals(Constants.RESOLUTION_DIRECTIVE))
+            {
+                m_isOptional = m_directives[i].getValue().equals(Constants.RESOLUTION_OPTIONAL);
+            }
+        }
+
+        // Parse mandatory directive and mark specified
+        // attributes as mandatory.
+        StringTokenizer tok = new StringTokenizer(mandatory, "");
+        while (tok.hasMoreTokens())
+        {
+            // Get attribute name.
+            String attrName = tok.nextToken().trim();
+            // Find attribute and mark it as mandatory.
+            boolean found = false;
+            for (int i = 0; (!found) && (i < m_attrs.length); i++)
+            {
+                if (m_attrs[i].getName().equals(attrName))
+                {
+                    m_attrs[i] = new R4Attribute(
+                        m_attrs[i].getName(), m_attrs[i].getValue(), true);
+                    found = true;
+                }
+            }
+            // If a specified mandatory attribute was not found,
+            // then error.
+            if (!found)
+            {
+                throw new IllegalArgumentException(
+                    "Mandatory attribute '" + attrName + "' does not exist.");
+            }
+        }
+
+        // Find and parse version attribute, if present.
+        String versionInterval = "0.0.0";
+        for (int i = 0; i < m_attrs.length; i++)
+        {
+            if (m_attrs[i].getName().equals(Constants.VERSION_ATTRIBUTE) ||
+                m_attrs[i].getName().equals(Constants.PACKAGE_SPECIFICATION_VERSION))
+            {
+                // Normalize version attribute name.
+                m_attrs[i] = new R4Attribute(
+                    Constants.VERSION_ATTRIBUTE, m_attrs[i].getValue(),
+                    m_attrs[i].isMandatory());
+                versionInterval = m_attrs[i].getValue();
+                break;
+            }
+        }
+        
+        IVersion[] versions = parseVersionInterval(versionInterval);
+        m_versionLow = versions[0];
+        if (versions.length == 2)
+        {
+            m_versionHigh = versions[1];
+        }
+    }
+
+    public String getId()
+    {
+        return m_id;
+    }
+
+    public IDirective[] getDirectives()
+    {
+        return m_directives;
+    }
+
+    public IAttribute[] getAttributes()
+    {
+        return m_attrs;
+    }
+
+    public IVersion getVersionLow()
+    {
+        return m_versionLow;
+    }
+
+    public IVersion getVersionHigh()
+    {
+        return m_versionHigh;
+    }
+
+    public boolean isOptional()
+    {
+        return m_isOptional;
+    }
+
+    // PREVIOUSLY PART OF COMPATIBILITY POLICY.
+    public boolean doesSatisfy(IPackage pkg)
+    {
+        // For packages to be compatible, they must have the
+        // same name.
+        if (!m_id.equals(pkg.getId()))
+        {
+            return false;
+        }
+        
+        return isVersionInRange(m_versionLow, pkg.getVersionLow(), pkg.getVersionHigh())
+            && doAttributesMatch(pkg);
+    }
+
+    // PREVIOUSLY PART OF COMPATIBILITY POLICY.
+    public static boolean isVersionInRange(IVersion version, IVersion low, IVersion high)
+    {
+        // We might not have an upper end to the range.
+        if (high == null)
+        {
+            return (version.compareTo(low) >= 0);
+        }
+        else if (low.isInclusive() && high.isInclusive())
+        {
+            return (version.compareTo(low) >= 0) && (version.compareTo(high) <= 0);
+        }
+        else if (high.isInclusive())
+        {
+            return (version.compareTo(low) > 0) && (version.compareTo(high) <= 0);
+        }
+        else if (low.isInclusive())
+        {
+            return (version.compareTo(low) >= 0) && (version.compareTo(high) < 0);
+        }
+
+        return (version.compareTo(low) > 0) && (version.compareTo(high) < 0);
+    }
+
+    private boolean doAttributesMatch(IPackage pkg)
+    {
+        // Cycle through all attributes of the specified package
+        // and make sure their values match the attribute values
+        // of this package.
+        for (int attrIdx = 0; attrIdx < pkg.getAttributes().length; attrIdx++)
+        {
+            // Get current attribute from specified package.
+            IAttribute attr = pkg.getAttributes()[attrIdx];
+
+            // Ignore version attribute, since it is a special case that
+            // has already been compared using isVersionInRange() before
+            // the call to this method was made.
+            if (attr.getName().equals(Constants.VERSION_ATTRIBUTE))
+            {
+                continue;
+            }
+
+            // Check if this package has the same attribute.
+            boolean found = false;
+            for (int thisAttrIdx = 0;
+                (!found) && (thisAttrIdx < m_attrs.length);
+                thisAttrIdx++)
+            {
+                // Get current attribute for this package.
+                IAttribute thisAttr = m_attrs[thisAttrIdx];
+                // Check if the attribute names are equal.
+                if (attr.getName().equals(thisAttr.getName()))
+                {
+                    // If the values are not equal, then return false immediately.
+                    // We should not compare version values here, since they are
+                    // a special case and have already been compared by a call to
+                    // isVersionInRange() before getting here; however, it is
+                    // possible for version to be mandatory, so make sure it is
+                    // present below.
+                    if (!attr.getValue().equals(thisAttr.getValue()))
+                    {
+                        return false;
+                    }
+                    found = true;
+                }
+            }
+            // If the attribute was not found, then return false.
+            if (!found)
+            {
+                return false;
+            }
+        }
+
+        // Now, cycle through all attributes of this package and verify that
+        // all mandatory attributes are present in the speceified package.
+        for (int thisAttrIdx = 0; thisAttrIdx < m_attrs.length; thisAttrIdx++)
+        {
+            // Get current attribute for this package.
+            IAttribute thisAttr = m_attrs[thisAttrIdx];
+            
+            // If the attribute is mandatory, then make sure
+            // the specified package has the attribute.
+            if (thisAttr.isMandatory())
+            {
+                boolean found = false;
+                for (int attrIdx = 0;
+                    (!found) && (attrIdx < pkg.getAttributes().length);
+                    attrIdx++)
+                {
+                    // Get current attribute from specified package.
+                    IAttribute attr = pkg.getAttributes()[attrIdx];
+        
+                    // Check if the attribute names are equal
+                    // and set found flag.
+                    if (thisAttr.getName().equals(attr.getName()))
+                    {
+                        found = true;
+                    }
+                }
+                // If not found, then return false.
+                if (!found)
+                {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public String toString()
+    {
+        String msg = getId();
+        for (int i = 0; (m_directives != null) && (i < m_directives.length); i++)
+        {
+            msg = msg + " [" + m_directives[i].getName() + ":="+ m_directives[i].getName() + "]";
+        }
+        for (int i = 0; (m_attrs != null) && (i < m_attrs.length); i++)
+        {
+            msg = msg + " [" + m_attrs[i].getValue() + "="+ m_attrs[i].getValue() + "]";
+        }
+        return msg;
+    }
+
+    // Like this: pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2,
+    //            pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+    public static IPackage[] parseImportOrExportHeader(String s)
+    {
+        IPackage[] pkgs = null;
+        if (s != null)
+        {
+            if (s.length() == 0)
+            {
+                throw new IllegalArgumentException(
+                    "The import and export headers cannot be an empty string.");
+            }
+            String[] ss = parseDelimitedString(s, ","); // FelixConstants.CLASS_PATH_SEPARATOR
+            pkgs = parsePackageStrings(ss);
+        }
+        return (pkgs == null) ? new IPackage[0] : pkgs;
+    }
+
+    // Like this: pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+    public static IPackage[] parsePackageStrings(String[] ss)
+        throws IllegalArgumentException
+    {
+        if (ss == null)
+        {
+            return null;
+        }
+
+        List completeList = new ArrayList();
+        for (int ssIdx = 0; ssIdx < ss.length; ssIdx++)
+        {
+            // Break string into semi-colon delimited pieces.
+            String[] pieces = parseDelimitedString(
+                ss[ssIdx], ";"); // FelixConstants.PACKAGE_SEPARATOR
+
+            // Count the number of different packages; packages
+            // will not have an '=' in their string. This assumes
+            // that packages come first, before directives and
+            // attributes.
+            int pkgCount = 0;
+            for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++)
+            {
+                if (pieces[pieceIdx].indexOf('=') >= 0)
+                {
+                    break;
+                }
+                pkgCount++;
+            }
+
+            // Error if no packages were specified.
+            if (pkgCount == 0)
+            {
+                throw new IllegalArgumentException(
+                    "No packages specified on import: " + ss[ssIdx]);
+            }
+
+            // Parse the directives/attributes.
+            IDirective[] dirs = new IDirective[pieces.length - pkgCount];
+            IAttribute[] attrs = new IAttribute[pieces.length - pkgCount];
+            int dirCount = 0, attrCount = 0;
+            int idx = -1;
+            String sep = null;
+            for (int pieceIdx = pkgCount; pieceIdx < pieces.length; pieceIdx++)
+            {
+                // Check if it is a directive.
+                if ((idx = pieces[pieceIdx].indexOf(":=")) >= 0) // FelixConstants.DIRECTIVE_SEPARATOR
+                {
+                    sep = ":="; // FelixConstants.DIRECTIVE_SEPARATOR
+                }
+                // Check if it is an attribute.
+                else if ((idx = pieces[pieceIdx].indexOf("=")) >= 0) // FelixConstants.ATTRIBUTE_SEPARATOR
+                {
+                    sep = "="; // FelixConstants.ATTRIBUTE_SEPARATOR
+                }
+                // It is an error.
+                else
+                {
+                    throw new IllegalArgumentException(
+                        "Not a directive/attribute: " + ss[ssIdx]);
+                }
+
+                String key = pieces[pieceIdx].substring(0, idx).trim();
+                String value = pieces[pieceIdx].substring(idx + sep.length()).trim();
+
+                // Remove quotes, if value is quoted.
+                if (value.startsWith("\"") && value.endsWith("\""))
+                {
+                    value = value.substring(1, value.length() - 1);
+                }
+
+                // Save the directive/attribute in the appropriate array.
+                if (sep.equals(":=")) // FelixConstants.DIRECTIVE_SEPARATOR
+                {
+                    dirs[dirCount++] = new R4Directive(key, value);
+                }
+                else
+                {
+                    attrs[attrCount++] = new R4Attribute(key, value, false);
+                }
+            }
+
+            // Shrink directive array.
+            IDirective[] dirsFinal = new IDirective[dirCount];
+            System.arraycopy(dirs, 0, dirsFinal, 0, dirCount);
+            // Shrink attribute array.
+            IAttribute[] attrsFinal = new IAttribute[attrCount];
+            System.arraycopy(attrs, 0, attrsFinal, 0, attrCount);
+
+            // Create package attributes for each package and
+            // set directives/attributes. Add each package to
+            // completel list of packages.
+            IPackage[] pkgs = new IPackage[pkgCount];
+            for (int pkgIdx = 0; pkgIdx < pkgCount; pkgIdx++)
+            {
+                pkgs[pkgIdx] = new R4Package(pieces[pkgIdx], dirsFinal, attrsFinal);
+                completeList.add(pkgs[pkgIdx]);
+            }
+        }
+    
+        IPackage[] ips = (IPackage[])
+            completeList.toArray(new IPackage[completeList.size()]);
+        return ips;
+    }
+
+    public static IVersion[] parseVersionInterval(String interval)
+    {
+        // Check if the version is an interval.
+        if (interval.indexOf(',') >= 0)
+        {
+            String s = interval.substring(1, interval.length() - 1);
+            String vlo = s.substring(0, s.indexOf(','));
+            String vhi = s.substring(s.indexOf(',') + 1, s.length());
+            return new IVersion[] {
+                new R4Version(vlo, (interval.charAt(0) == '[')),
+                new R4Version(vhi, (interval.charAt(interval.length() - 1) == ']'))
+            };
+        }
+        else
+        {
+            return new IVersion[] { new R4Version(interval, 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 an array of string tokens or null if there were no tokens.
+    **/
+    public static String[] parseDelimitedString(String value, String delim)
+    {
+        if (value == null)
+        {
+           value = "";
+        }
+
+        List list = new ArrayList();
+
+        int CHAR = 1;
+        int DELIMITER = 2;
+        int STARTQUOTE = 4;
+        int ENDQUOTE = 8;
+
+        StringBuffer sb = new StringBuffer();
+
+        int expecting = (CHAR | DELIMITER | STARTQUOTE);
+        
+        for (int i = 0; i < value.length(); i++)
+        {
+            char c = value.charAt(i);
+
+            boolean isDelimiter = (delim.indexOf(c) >= 0);
+            boolean isQuote = (c == '"');
+
+            if (isDelimiter && ((expecting & DELIMITER) > 0))
+            {
+                list.add(sb.toString().trim());
+                sb.delete(0, sb.length());
+                expecting = (CHAR | DELIMITER | STARTQUOTE);
+            }
+            else if (isQuote && ((expecting & STARTQUOTE) > 0))
+            {
+                sb.append(c);
+                expecting = CHAR | ENDQUOTE;
+            }
+            else if (isQuote && ((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);
+            }
+        }
+
+        if (sb.length() > 0)
+        {
+            list.add(sb.toString().trim());
+        }
+
+        return (String[]) list.toArray(new String[list.size()]);
+    }
+}
\ No newline at end of file

Added: incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Version.java
URL: http://svn.apache.org/viewcvs/incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Version.java?rev=233031&view=auto
==============================================================================
--- incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Version.java (added)
+++ incubator/oscar/trunk/src/org/apache/osgi/bundle/bundlerepository/R4Version.java Tue Aug 16 11:33:34 2005
@@ -0,0 +1,216 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   Licensed 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.osgi.bundle.bundlerepository;
+
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.bundlerepository.IVersion;
+
+public class R4Version implements Comparable, IVersion
+{
+    private int m_major = 0;
+    private int m_minor = 0;
+    private int m_micro = 0;
+    private String m_qualifier = "";
+    private boolean m_isInclusive = true;
+
+    private static final String SEPARATOR = ".";
+
+    public R4Version(String versionString)
+    {
+        this(versionString, true);
+    }
+
+    public R4Version(String versionString, boolean isInclusive)
+    {
+        if (versionString == null)
+        {
+            versionString = "0.0.0";
+        }
+        Object[] objs = parseVersion(versionString);
+        m_major = ((Integer) objs[0]).intValue();
+        m_minor = ((Integer) objs[1]).intValue();
+        m_micro = ((Integer) objs[2]).intValue();
+        m_qualifier = (String) objs[3];
+        m_isInclusive = isInclusive;
+    }
+
+    private static Object[] parseVersion(String versionString)
+    {
+        String s = versionString.trim();
+        Object[] objs = new Object[4];
+        objs[0] = objs[1] = objs[2] = new Integer(0);
+        objs[3] = "";
+        StringTokenizer tok = new StringTokenizer(s, SEPARATOR);
+        try
+        {
+            objs[0] = Integer.valueOf(tok.nextToken());
+            if (tok.hasMoreTokens())
+            {
+                objs[1] = Integer.valueOf(tok.nextToken());
+                if (tok.hasMoreTokens())
+                {
+                    objs[2] = Integer.valueOf(tok.nextToken());
+                    if (tok.hasMoreTokens())
+                    {
+                        objs[3] = tok.nextToken();
+                    }
+                }
+            }
+        }
+        catch (NumberFormatException ex)
+        {
+            throw new IllegalArgumentException("Invalid version: " + versionString);
+        }
+
+        if ((((Integer) objs[0]).intValue() < 0) ||
+            (((Integer) objs[0]).intValue() < 0) ||
+            (((Integer) objs[0]).intValue() < 0))
+        {
+            throw new IllegalArgumentException("Invalid version: " + versionString);
+        }
+
+        return objs;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#equals(java.lang.Object)
+    **/
+    public boolean equals(Object object)
+    {
+        if (!(object instanceof R4Version))
+        {
+            return false;
+        }
+        IVersion v = (IVersion) object;
+        return
+            (v.getMajorComponent() == m_major) &&
+            (v.getMinorComponent() == m_minor) &&
+            (v.getMicroComponent() == m_micro) &&
+            (v.getQualifierComponent().equals(m_qualifier));
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#getMajorComponent()
+    **/
+    public int getMajorComponent()
+    {
+        return m_major;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#getMinorComponent()
+    **/
+    public int getMinorComponent()
+    {
+        return m_minor;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#getMicroComponent()
+    **/
+    public int getMicroComponent()
+    {
+        return m_micro;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#getQualifierComponent()
+    **/
+    public String getQualifierComponent()
+    {
+        return m_qualifier;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#isInclusive()
+    **/
+    public boolean isInclusive()
+    {
+        return m_isInclusive;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#compareTo(java.lang.Object)
+    **/
+    public int compareTo(Object o)
+    {
+        if (!(o instanceof R4Version))
+            throw new ClassCastException();
+
+        if (equals(o))
+            return 0;
+
+        if (isGreaterThan((IVersion) o))
+            return 1;
+
+        return -1;
+    }
+
+    private boolean isGreaterThan(IVersion v)
+    {
+        if (v == null)
+        {
+            return false;
+        }
+
+        if (m_major > v.getMajorComponent())
+        {
+            return true;
+        }
+        if (m_major < v.getMajorComponent())
+        {
+            return false;
+        }
+        if (m_minor > v.getMinorComponent())
+        {
+            return true;
+        }
+        if (m_minor < v.getMinorComponent())
+        {
+            return false;
+        }
+        if (m_micro > v.getMicroComponent())
+        {
+            return true;
+        }
+        if (m_micro < v.getMicroComponent())
+        {
+            return false;
+        }
+        if (m_qualifier.compareTo(v.getQualifierComponent()) > 0)
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#toString()
+    **/
+    public String toString()
+    {
+        if (m_qualifier.length() == 0)
+        {
+            return m_major + "." + m_minor + "." + m_micro; 
+        }
+        return m_major + "." + m_minor + "." + m_micro + "." + m_qualifier; 
+    }
+}
\ No newline at end of file