Initial source commit.
git-svn-id: https://svn.apache.org/repos/asf/incubator/oscar/trunk@233031 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/org/apache/osgi/bundle/bundlerepository/Activator.java b/src/org/apache/osgi/bundle/bundlerepository/Activator.java
new file mode 100644
index 0000000..c50192e
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/Activator.java
@@ -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.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator
+{
+ private transient BundleContext m_context = null;
+ private transient BundleRepositoryImpl m_br = null;
+
+ public void start(BundleContext context)
+ {
+ m_context = context;
+
+ // Register bundle repository service.
+ m_br = new BundleRepositoryImpl(m_context);
+ context.registerService(
+ org.apache.osgi.service.bundlerepository.BundleRepository.class.getName(),
+ m_br, null);
+
+ // We dynamically import the shell service API, so it
+ // might not actually be available, so be ready to catch
+ // the exception when we try to register the command service.
+ try
+ {
+ // Register "obr" shell command service as a
+ // wrapper for the bundle repository service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new ObrCommandImpl(m_context, m_br), null);
+ }
+ catch (Throwable th)
+ {
+ // Ignore.
+ }
+ }
+
+ public void stop(BundleContext context)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/BundleRepositoryImpl.java b/src/org/apache/osgi/bundle/bundlerepository/BundleRepositoryImpl.java
new file mode 100644
index 0000000..07f5828
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/BundleRepositoryImpl.java
@@ -0,0 +1,274 @@
+/*
+ * 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.PrintStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.osgi.service.bundlerepository.*;
+import org.osgi.framework.*;
+
+public class BundleRepositoryImpl implements BundleRepository
+{
+ private BundleContext m_context = null;
+ private RepositoryState m_repo = null;
+
+ public BundleRepositoryImpl(BundleContext context)
+ {
+ m_context = context;
+ m_repo = new RepositoryState(m_context);
+ }
+
+ public String[] getRepositoryURLs()
+ {
+ return m_repo.getURLs();
+ }
+
+ public synchronized void setRepositoryURLs(String[] urls)
+ {
+ m_repo.setURLs(urls);
+ }
+
+ /**
+ * Get the number of bundles available in the repository.
+ * @return the number of available bundles.
+ **/
+ public synchronized BundleRecord[] getBundleRecords()
+ {
+ return m_repo.getRecords();
+ }
+
+ /**
+ * Get the specified bundle record from the repository.
+ * @param i the bundle record index to retrieve.
+ * @return the associated bundle record or <tt>null</tt>.
+ **/
+ public synchronized BundleRecord[] getBundleRecords(String symName)
+ {
+ return m_repo.getRecords(symName);
+ }
+
+ /**
+ * Get bundle record for the bundle with the specified name
+ * and version from the repository.
+ * @param name the bundle record name to retrieve.
+ * @param version three-interger array of the version associated with
+ * the name to retrieve.
+ * @return the associated bundle record or <tt>null</tt>.
+ **/
+ public synchronized BundleRecord getBundleRecord(String symName, int[] version)
+ {
+ return m_repo.getRecord(symName, version);
+ }
+
+ public boolean deployBundle(
+ PrintStream out, PrintStream err, String symName, int[] version,
+ boolean isResolve, boolean isStart)
+ {
+ // List to hold bundles that need to be started.
+ List startList = null;
+
+ // Get the bundle record of the remote bundle to be deployed.
+ BundleRecord targetRecord = m_repo.getRecord(symName, version);
+ if (targetRecord == null)
+ {
+ err.println("No such bundle in repository.");
+ return false;
+ }
+
+ // Create an editable snapshot of the current set of
+ // locally installed bundles.
+ LocalState localState = new LocalState(m_context);
+
+ // If the precise bundle is already deployed, then we are done.
+ if (localState.findBundle(symName, version) != null)
+ {
+ return true;
+ }
+
+ // Create the transitive closure all bundles that must be
+ // deployed as a result of deploying the target bundle;
+ // use a list because this will keep everything in order.
+ BundleRecord[] deployRecords = null;
+ // If the resolve flag is set, then get its imports to
+ // calculate the transitive closure of its dependencies.
+ if (isResolve)
+ {
+// Package[] imports = (Package[])
+// targetRecord.getAttribute(BundleRecord.IMPORT_PACKAGE);
+ Filter[] reqs = (Filter[])
+ targetRecord.getAttribute("requirement");
+ try
+ {
+ deployRecords = m_repo.resolvePackages(localState, reqs);
+ }
+ catch (ResolveException ex)
+ {
+ err.println("Resolve error: " + ex.getPackage());
+ return false;
+ }
+ }
+
+ // Add the target bundle since it will not be
+ // included in the array of records to deploy.
+ if (deployRecords == null)
+ {
+ deployRecords = new BundleRecord[] { targetRecord };
+ }
+ else
+ {
+ // Create a new array containing the target and put it first,
+ // since the array will be process in reverse order.
+ BundleRecord[] newRecs = new BundleRecord[deployRecords.length + 1];
+ newRecs[0] = targetRecord;
+ System.arraycopy(deployRecords, 0, newRecs, 1, deployRecords.length);
+ deployRecords = newRecs;
+ }
+
+ // Now iterate through all bundles in the deploy list
+ // in reverse order and deploy each; the order is not
+ // so important, but by reversing them at least the
+ // dependencies will be printed first and perhaps it
+ // will avoid some ordering issues when we are starting
+ // bundles.
+ for (int i = 0; i < deployRecords.length; i++)
+ {
+ LocalState.LocalBundleRecord updateRecord =
+ localState.findUpdatableBundle(deployRecords[i]);
+ if (updateRecord != null)
+ {
+// TODO: Should check to make sure that update bundle isn't already the
+// correct version.
+ // Modify our copy of the local state to reflect
+ // that the bundle is now updated.
+ localState.update(updateRecord, deployRecords[i]);
+
+ // Print out an "updating" message.
+ if (deployRecords[i] != targetRecord)
+ {
+ out.print("Updating dependency: ");
+ }
+ else
+ {
+ out.print("Updating: ");
+ }
+ out.println(Util.getBundleName(updateRecord.getBundle()));
+
+ // Actually perform the update.
+ try
+ {
+ URL url = new URL(
+ (String) deployRecords[i].getAttribute(BundleRecord.BUNDLE_URL));
+ updateRecord.getBundle().update(url.openStream());
+
+ // If necessary, save the updated bundle to be
+ // started later.
+ if (isStart)
+ {
+ if (startList == null)
+ {
+ startList = new ArrayList();
+ }
+ startList.add(updateRecord.getBundle());
+ }
+ }
+ catch (Exception ex)
+ {
+ err.println("Update error: " + Util.getBundleName(updateRecord.getBundle()));
+ ex.printStackTrace(err);
+ return false;
+ }
+ }
+ else
+ {
+ // Print out an "installing" message.
+ if (deployRecords[i] != targetRecord)
+ {
+ out.print("Installing dependency: ");
+ }
+ else
+ {
+ out.print("Installing: ");
+ }
+ out.println(deployRecords[i].getAttribute(BundleRecord.BUNDLE_NAME));
+
+ try
+ {
+ // Actually perform the install, but do not use the actual
+ // bundle JAR URL for the bundle location, since this will
+ // limit OBR's ability to manipulate bundle versions. Instead,
+ // use a unique timestamp as the bundle location.
+ URL url = new URL(
+ (String) deployRecords[i].getAttribute(BundleRecord.BUNDLE_URL));
+ Bundle bundle = m_context.installBundle(
+ "obr://"
+ + deployRecords[i].getAttribute(BundleRecord.BUNDLE_NAME)
+ + "/" + System.currentTimeMillis(),
+ url.openStream());
+
+ // If necessary, save the installed bundle to be
+ // started later.
+ if (isStart)
+ {
+ if (startList == null)
+ {
+ startList = new ArrayList();
+ }
+ startList.add(bundle);
+ }
+ }
+ catch (Exception ex)
+ {
+ err.println("Install error: "
+ + deployRecords[i].getAttribute(BundleRecord.BUNDLE_NAME));
+ ex.printStackTrace(err);
+ return false;
+ }
+ }
+ }
+
+ // If necessary, start bundles after installing them all.
+ if (isStart)
+ {
+ for (int i = 0; (startList != null) && (i < startList.size()); i++)
+ {
+ Bundle bundle = (Bundle) startList.get(i);
+ try
+ {
+ bundle.start();
+ }
+ catch (BundleException ex)
+ {
+ err.println("Update error: " + Util.getBundleName(bundle));
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public BundleRecord[] resolvePackages(IPackage[] pkgs)
+ throws ResolveException
+ {
+// TODO: FIX
+// return m_repo.resolvePackages(new LocalState(m_context), pkgs);
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/FileUtil.java b/src/org/apache/osgi/bundle/bundlerepository/FileUtil.java
new file mode 100644
index 0000000..9f27a0d
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/FileUtil.java
@@ -0,0 +1,168 @@
+/*
+ * 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.net.URL;
+import java.net.URLConnection;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+public class FileUtil
+{
+ public static void downloadSource(
+ PrintStream out, PrintStream err,
+ String srcURL, String dirStr, boolean extract)
+ {
+ // Get the file name from the URL.
+ String fileName = (srcURL.lastIndexOf('/') > 0)
+ ? srcURL.substring(srcURL.lastIndexOf('/') + 1)
+ : srcURL;
+
+ try
+ {
+ out.println("Connecting...");
+
+ File dir = new File(dirStr);
+ if (!dir.exists())
+ {
+ err.println("Destination directory does not exist.");
+ }
+ File file = new File(dir, fileName);
+
+ OutputStream os = new FileOutputStream(file);
+ URLConnection conn = new URL(srcURL).openConnection();
+ int total = conn.getContentLength();
+ InputStream is = conn.getInputStream();
+
+ if (total > 0)
+ {
+ out.println("Downloading " + fileName
+ + " ( " + total + " bytes ).");
+ }
+ else
+ {
+ out.println("Downloading " + fileName + ".");
+ }
+ byte[] buffer = new byte[4096];
+ int count = 0;
+ for (int len = is.read(buffer); len > 0; len = is.read(buffer))
+ {
+ count += len;
+ os.write(buffer, 0, len);
+ }
+
+ os.close();
+ is.close();
+
+ if (extract)
+ {
+ is = new FileInputStream(file);
+ JarInputStream jis = new JarInputStream(is);
+ out.println("Extracting...");
+ unjar(jis, dir);
+ jis.close();
+ file.delete();
+ }
+ }
+ catch (Exception ex)
+ {
+ err.println(ex);
+ }
+ }
+
+ public static void unjar(JarInputStream jis, File dir)
+ throws IOException
+ {
+ // Reusable buffer.
+ byte[] buffer = new byte[4096];
+
+ // Loop through JAR entries.
+ for (JarEntry je = jis.getNextJarEntry();
+ je != null;
+ je = jis.getNextJarEntry())
+ {
+ if (je.getName().startsWith("/"))
+ {
+ throw new IOException("JAR resource cannot contain absolute paths.");
+ }
+
+ File target = new File(dir, je.getName());
+
+ // Check to see if the JAR entry is a directory.
+ if (je.isDirectory())
+ {
+ if (!target.exists())
+ {
+ if (!target.mkdirs())
+ {
+ throw new IOException("Unable to create target directory: "
+ + target);
+ }
+ }
+ // Just continue since directories do not have content to copy.
+ continue;
+ }
+
+ int lastIndex = je.getName().lastIndexOf('/');
+ String name = (lastIndex >= 0) ?
+ je.getName().substring(lastIndex + 1) : je.getName();
+ String destination = (lastIndex >= 0) ?
+ je.getName().substring(0, lastIndex) : "";
+
+ // JAR files use '/', so convert it to platform separator.
+ destination = destination.replace('/', File.separatorChar);
+ copy(jis, dir, name, destination, buffer);
+ }
+ }
+
+ public static void copy(
+ InputStream is, File dir, String destName, String destDir, byte[] buffer)
+ throws IOException
+ {
+ if (destDir == null)
+ {
+ destDir = "";
+ }
+
+ // Make sure the target directory exists and
+ // that is actually a directory.
+ File targetDir = new File(dir, destDir);
+ if (!targetDir.exists())
+ {
+ if (!targetDir.mkdirs())
+ {
+ throw new IOException("Unable to create target directory: "
+ + targetDir);
+ }
+ }
+ else if (!targetDir.isDirectory())
+ {
+ throw new IOException("Target is not a directory: "
+ + targetDir);
+ }
+
+ BufferedOutputStream bos = new BufferedOutputStream(
+ new FileOutputStream(new File(targetDir, destName)));
+ int count = 0;
+ while ((count = is.read(buffer)) > 0)
+ {
+ bos.write(buffer, 0, count);
+ }
+ bos.close();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/IteratorToEnumeration.java b/src/org/apache/osgi/bundle/bundlerepository/IteratorToEnumeration.java
new file mode 100644
index 0000000..edd749f
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/IteratorToEnumeration.java
@@ -0,0 +1,44 @@
+/*
+ * 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.Enumeration;
+import java.util.Iterator;
+
+public class IteratorToEnumeration implements Enumeration
+{
+ private Iterator m_iter = null;
+
+ public IteratorToEnumeration(Iterator iter)
+ {
+ m_iter = iter;
+ }
+
+ public boolean hasMoreElements()
+ {
+ if (m_iter == null)
+ return false;
+ return m_iter.hasNext();
+ }
+
+ public Object nextElement()
+ {
+ if (m_iter == null)
+ return null;
+ return m_iter.next();
+ }
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/LocalState.java b/src/org/apache/osgi/bundle/bundlerepository/LocalState.java
new file mode 100644
index 0000000..6c91952
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/LocalState.java
@@ -0,0 +1,378 @@
+/*
+ * 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.BundleRecord;
+import org.apache.osgi.service.bundlerepository.IPackage;
+import org.osgi.framework.*;
+
+public class LocalState
+{
+ private BundleContext m_context = null;
+ private List m_localRecordList = new ArrayList();
+
+ public LocalState(BundleContext context)
+ {
+ m_context = context;
+ initialize();
+ }
+
+ public BundleRecord findBundle(String symName, int[] version)
+ {
+ for (int i = 0; i < m_localRecordList.size(); i++)
+ {
+ BundleRecord brLocal = (BundleRecord) m_localRecordList.get(i);
+ String localSymName = (String)
+ brLocal.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+ int[] localVersion = Util.parseVersionString((String)
+ brLocal.getAttribute(BundleRecord.BUNDLE_VERSION));
+ if ((localSymName != null) &&
+ localSymName.equals(symName) &&
+ (Util.compareVersion(localVersion, version) == 0))
+ {
+ return brLocal;
+ }
+ }
+ return null;
+ }
+
+ public BundleRecord[] findBundles(String symName)
+ {
+ List matchList = new ArrayList();
+ for (int i = 0; i < m_localRecordList.size(); i++)
+ {
+ BundleRecord brLocal = (BundleRecord) m_localRecordList.get(i);
+ String localSymName = (String)
+ brLocal.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+ if ((localSymName != null) && localSymName.equals(symName))
+ {
+ matchList.add(brLocal);
+ }
+ }
+ return (BundleRecord[]) matchList.toArray(new BundleRecord[matchList.size()]);
+ }
+
+ public void update(BundleRecord oldRecord, BundleRecord newRecord)
+ {
+ // To update the old record we need to replace it with
+ // a new one, since BundleRecords are immutable. Make
+ // a new record that contains the attributes of the new
+ // record, but is associated with the local bundle of
+ // the old record.
+ if (oldRecord instanceof LocalBundleRecord)
+ {
+ String[] keys = newRecord.getAttributes();
+ Map map = new HashMap();
+ for (int i = 0; i < keys.length; i++)
+ {
+ map.put(keys, newRecord.getAttribute(keys[i]));
+ }
+ BundleRecord updatedRecord =
+ new LocalBundleRecord(
+ map, ((LocalBundleRecord) oldRecord).getBundle());
+ int idx = m_localRecordList.indexOf(oldRecord);
+ if (idx >= 0)
+ {
+ m_localRecordList.set(idx, updatedRecord);
+ }
+ }
+ }
+
+ public LocalBundleRecord findUpdatableBundle(BundleRecord record)
+ {
+ // Determine if any bundles with the specified symbolic
+ // name are already installed locally.
+ BundleRecord[] localRecords = findBundles(
+ (String)record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME));
+ if (localRecords != null)
+ {
+ // Since there are local bundles with the same symbolic
+ // name installed, then we must determine if we can
+ // update an existing bundle or if we must install
+ // another one. Loop through all local bundles with same
+ // symbolic name and find the first one that can be updated
+ // without breaking constraints of existing bundles.
+ for (int i = 0; i < localRecords.length; i++)
+ {
+ // Check to make sure that the version of the target
+ // record is greater than the local bundle version,
+ // since we do not want to perform a downgrade.
+// int[] vLocal = Util.parseVersionString((String)
+// localRecords[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+// int[] vTarget = Util.parseVersionString((String)
+// record.getAttribute(BundleRecord.BUNDLE_VERSION));
+// TODO: VERIFY WHAT IS GOING ON HERE.
+ // If the target bundle is a newer version and it is
+ // export compatible with the local bundle, then return it.
+ if (isUpdatable(localRecords[i], record))
+ {
+ return (LocalBundleRecord) localRecords[i];
+ }
+ }
+ }
+ return null;
+ }
+
+ public boolean isUpdatable(BundleRecord oldVersion, BundleRecord newVersion)
+ {
+ // First get all of the potentially resolvable package declarations
+ // from the local bundles for the old version of the bundle.
+ Filter[] reqFilters = getResolvableImportDeclarations(oldVersion);
+ if (reqFilters == null)
+ {
+ return true;
+ }
+ // Now make sure that all of the resolvable import declarations
+ // for the old version of the bundle can also be satisfied by
+ // the new version of the bundle.
+ Map[] capMaps = (Map[])
+ newVersion.getAttribute("capability");
+ if (capMaps == null)
+ {
+ return false;
+ }
+ MapToDictionary mapDict = new MapToDictionary(null);
+ for (int reqIdx = 0; reqIdx < reqFilters.length; reqIdx++)
+ {
+ boolean satisfied = false;
+ for (int capIdx = 0; !satisfied && (capIdx < capMaps.length); capIdx++)
+ {
+ mapDict.setSourceMap(capMaps[capIdx]);
+ if (reqFilters[reqIdx].match(mapDict))
+ {
+ satisfied = true;
+ }
+ }
+
+ // If any of the previously resolvable package declarations
+ // cannot be resolved, then the bundle is not updatable.
+ if (!satisfied)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public Filter[] getResolvableImportDeclarations(BundleRecord record)
+ {
+ Map[] capMaps = (Map[])
+ record.getAttribute("capability");
+ if ((capMaps != null) && (capMaps.length > 0))
+ {
+ List filterList = new ArrayList();
+ // Use brute force to determine if any of the exports
+ // could possibly resolve any of the imports.
+ MapToDictionary mapDict = new MapToDictionary(null);
+ for (int capIdx = 0; capIdx < capMaps.length; capIdx++)
+ {
+ boolean added = false;
+ for (int recIdx = 0; !added && (recIdx < m_localRecordList.size()); recIdx++)
+ {
+ BundleRecord brLocal = (BundleRecord) m_localRecordList.get(recIdx);
+ Filter[] reqFilters = (Filter[])
+ brLocal.getAttribute("requirement");
+ for (int reqIdx = 0;
+ (reqFilters != null) && (reqIdx < reqFilters.length);
+ reqIdx++)
+ {
+ mapDict.setSourceMap(capMaps[capIdx]);
+ if (reqFilters[reqIdx].match(mapDict))
+ {
+ added = true;
+ filterList.add(reqFilters[reqIdx]);
+ }
+ }
+ }
+ }
+ return (Filter[])
+ filterList.toArray(new Filter[filterList.size()]);
+ }
+ return null;
+ }
+
+ public boolean isResolvable(Filter reqFilter)
+ {
+ MapToDictionary mapDict = new MapToDictionary(null);
+ for (int brIdx = 0; brIdx < m_localRecordList.size(); brIdx++)
+ {
+ BundleRecord brLocal = (BundleRecord) m_localRecordList.get(brIdx);
+ Map[] capMaps = (Map[]) brLocal.getAttribute("capability");
+ for (int capIdx = 0; (capMaps != null) && (capIdx < capMaps.length); capIdx++)
+ {
+ mapDict.setSourceMap(capMaps[capIdx]);
+ if (reqFilter.match(mapDict))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void initialize()
+ {
+ Bundle[] bundles = m_context.getBundles();
+ for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+ {
+ Dictionary dict = bundles[i].getHeaders();
+ // Create a case-insensitive map.
+ Map bundleMap = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+
+ for (Enumeration keys = dict.keys(); keys.hasMoreElements(); )
+ {
+ Object key = keys.nextElement();
+ bundleMap.put(key, dict.get(key));
+ }
+
+ // Remove and convert any import package declarations
+ // into requirement filters.
+ String target = (String) bundleMap.remove(BundleRecord.IMPORT_PACKAGE);
+ if (target != null)
+ {
+ IPackage[] pkgs = R4Package.parseImportOrExportHeader(target);
+ Filter[] filters = new Filter[(pkgs == null) ? 0 : pkgs.length];
+ for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+ {
+ try
+ {
+ String low = pkgs[pkgIdx].getVersionLow().isInclusive()
+ ? "(version>=" + pkgs[pkgIdx].getVersionLow() + ")"
+ : "(!(version<=" + pkgs[pkgIdx].getVersionLow() + ")";
+
+ if (pkgs[pkgIdx].getVersionHigh() != null)
+ {
+ String high = pkgs[pkgIdx].getVersionHigh().isInclusive()
+ ? "(version<=" + pkgs[pkgIdx].getVersionHigh() + ")"
+ : "(!(version>=" + pkgs[pkgIdx].getVersionHigh() + ")";
+ filters[pkgIdx] = m_context.createFilter(
+ "(&(type=Export-Package)(name="
+ + pkgs[pkgIdx].getId() + ")"
+ + low + high + ")");
+ }
+ else
+ {
+ filters[pkgIdx] = m_context.createFilter(
+ "(&(type=Export-Package)(name="
+ + pkgs[pkgIdx].getId() + ")"
+ + low + ")");
+ }
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ // Ignore, since it should not happen.
+ }
+ }
+ bundleMap.put("requirement", filters);
+ }
+
+ // Remove and convert any export package declarations
+ // into capability maps.
+ target = (String) bundleMap.remove(BundleRecord.EXPORT_PACKAGE);
+ if (target != null)
+ {
+ IPackage[] pkgs = R4Package.parseImportOrExportHeader(target);
+ Map[] capMaps = new Map[(pkgs == null) ? 0 : pkgs.length];
+ for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+ {
+ // Create a case-insensitive map.
+ capMaps[pkgIdx] = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ capMaps[pkgIdx].put("type", "Export-Package");
+ capMaps[pkgIdx].put("name", pkgs[pkgIdx].getId());
+ capMaps[pkgIdx].put("version", pkgs[pkgIdx].getVersionLow());
+ }
+ bundleMap.put("capability", capMaps);
+ }
+
+ // For the system bundle, add a special platform capability.
+ if (bundles[i].getBundleId() == 0)
+ {
+ // Create a case-insensitive map.
+ Map map = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ map.put(
+ Constants.FRAMEWORK_VERSION,
+ m_context.getProperty(Constants.FRAMEWORK_VERSION));
+ map.put(
+ Constants.FRAMEWORK_VENDOR,
+ m_context.getProperty(Constants.FRAMEWORK_VENDOR));
+ map.put(
+ Constants.FRAMEWORK_LANGUAGE,
+ m_context.getProperty(Constants.FRAMEWORK_LANGUAGE));
+ map.put(
+ Constants.FRAMEWORK_OS_NAME,
+ m_context.getProperty(Constants.FRAMEWORK_OS_NAME));
+ map.put(
+ Constants.FRAMEWORK_OS_VERSION,
+ m_context.getProperty(Constants.FRAMEWORK_OS_VERSION));
+ map.put(
+ Constants.FRAMEWORK_PROCESSOR,
+ m_context.getProperty(Constants.FRAMEWORK_PROCESSOR));
+// map.put(
+// FelixConstants.FELIX_VERSION_PROPERTY,
+// m_context.getProperty(FelixConstants.FELIX_VERSION_PROPERTY));
+ Map[] capMaps = (Map[]) bundleMap.get("capability");
+ if (capMaps == null)
+ {
+ capMaps = new Map[] { map };
+ }
+ else
+ {
+ Map[] newCaps = new Map[capMaps.length + 1];
+ newCaps[0] = map;
+ System.arraycopy(capMaps, 0, newCaps, 1, capMaps.length);
+ capMaps = newCaps;
+ }
+ bundleMap.put("capability", capMaps);
+ }
+ m_localRecordList.add(new LocalBundleRecord(bundleMap, bundles[i]));
+ }
+ }
+
+ public static class LocalBundleRecord extends BundleRecord
+ {
+ private Bundle m_bundle = null;
+
+ LocalBundleRecord(Map attrMap, Bundle bundle)
+ {
+ super(attrMap);
+ m_bundle = bundle;
+ }
+
+ public Bundle getBundle()
+ {
+ return m_bundle;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/MapToDictionary.java b/src/org/apache/osgi/bundle/bundlerepository/MapToDictionary.java
new file mode 100644
index 0000000..d7806c6
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/MapToDictionary.java
@@ -0,0 +1,97 @@
+/*
+ * 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.*;
+
+
+/**
+ * This is a simple class that implements a <tt>Dictionary</tt>
+ * from a <tt>Map</tt>. The resulting dictionary is immutatable.
+**/
+public class MapToDictionary extends Dictionary
+{
+ /**
+ * Map source.
+ **/
+ private Map m_map = null;
+
+ public MapToDictionary(Map map)
+ {
+ m_map = map;
+ }
+
+ public void setSourceMap(Map map)
+ {
+ m_map = map;
+ }
+
+ public Enumeration elements()
+ {
+ if (m_map == null)
+ {
+ return null;
+ }
+ return new IteratorToEnumeration(m_map.values().iterator());
+ }
+
+ public Object get(Object key)
+ {
+ if (m_map == null)
+ {
+ return null;
+ }
+ return m_map.get(key);
+ }
+
+ public boolean isEmpty()
+ {
+ if (m_map == null)
+ {
+ return true;
+ }
+ return m_map.isEmpty();
+ }
+
+ public Enumeration keys()
+ {
+ if (m_map == null)
+ {
+ return null;
+ }
+ return new IteratorToEnumeration(m_map.keySet().iterator());
+ }
+
+ public Object put(Object key, Object value)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object remove(Object key)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size()
+ {
+ if (m_map == null)
+ {
+ return 0;
+ }
+ return m_map.size();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java b/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java
new file mode 100644
index 0000000..10465a0
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java
@@ -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;
+ }
+ }
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java b/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java
new file mode 100644
index 0000000..d100711
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java
@@ -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
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java b/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java
new file mode 100644
index 0000000..719dd89
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java
@@ -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
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Package.java b/src/org/apache/osgi/bundle/bundlerepository/R4Package.java
new file mode 100644
index 0000000..a055133
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Package.java
@@ -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
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Version.java b/src/org/apache/osgi/bundle/bundlerepository/R4Version.java
new file mode 100644
index 0000000..eb44a9a
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Version.java
@@ -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
diff --git a/src/org/apache/osgi/bundle/bundlerepository/RepositoryState.java b/src/org/apache/osgi/bundle/bundlerepository/RepositoryState.java
new file mode 100644
index 0000000..b8b52d8
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/RepositoryState.java
@@ -0,0 +1,563 @@
+/*
+ * 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.net.*;
+import java.util.*;
+
+import org.apache.osgi.bundle.bundlerepository.kxmlsax.KXmlSAXParser;
+import org.apache.osgi.bundle.bundlerepository.metadataparser.MultivalueMap;
+import org.apache.osgi.bundle.bundlerepository.metadataparser.XmlCommonHandler;
+import org.apache.osgi.service.bundlerepository.BundleRecord;
+import org.apache.osgi.service.bundlerepository.ResolveException;
+import org.osgi.framework.*;
+
+public class RepositoryState
+{
+ private BundleContext m_context = null;
+ private String[] m_urls = null;
+ private Map m_recordMap = new HashMap();
+ private BundleRecord[] m_recordArray = null;
+ private boolean m_initialized = false;
+
+ private int m_hopCount = 1;
+
+ private static final String[] DEFAULT_REPOSITORY_URL = {
+ "http://oscar-osgi.sf.net/alpha/repository.xml"
+ };
+ public static final String REPOSITORY_URL_PROP = "osgi.repository.url";
+ public static final String EXTERN_REPOSITORY_TAG = "extern-repositories";
+
+ public RepositoryState(BundleContext context)
+ {
+ m_context = context;
+
+ String urlStr = m_context.getProperty(REPOSITORY_URL_PROP);
+ if (urlStr != null)
+ {
+ StringTokenizer st = new StringTokenizer(urlStr);
+ if (st.countTokens() > 0)
+ {
+ m_urls = new String[st.countTokens()];
+ for (int i = 0; i < m_urls.length; i++)
+ {
+ m_urls[i] = st.nextToken();
+ }
+ }
+ }
+
+ // Use the default URL if none were specified.
+ if (m_urls == null)
+ {
+ m_urls = DEFAULT_REPOSITORY_URL;
+ }
+ }
+
+ public String[] getURLs()
+ {
+ // Return a copy because the array is mutable.
+ return (m_urls == null) ? null : (String[]) m_urls.clone();
+ }
+
+ public void setURLs(String[] urls)
+ {
+ if (urls != null)
+ {
+ m_urls = urls;
+ initialize();
+ }
+ }
+
+ public BundleRecord[] getRecords()
+ {
+ if (!m_initialized)
+ {
+ initialize();
+ }
+
+ // Returned cached array of bundle records.
+ return m_recordArray;
+ }
+
+ public BundleRecord[] getRecords(String symName)
+ {
+ if (!m_initialized)
+ {
+ initialize();
+ }
+
+ // Return a copy of the array, since it would be mutable
+ // otherwise.
+ BundleRecord[] records = (BundleRecord[]) m_recordMap.get(symName);
+ // Return a copy because the array is mutable.
+ return (records == null) ? null : (BundleRecord[]) records.clone();
+ }
+
+ public BundleRecord getRecord(String symName, int[] version)
+ {
+ if (!m_initialized)
+ {
+ initialize();
+ }
+
+ BundleRecord[] records = (BundleRecord[]) m_recordMap.get(symName);
+ if ((records != null) && (records.length > 0))
+ {
+ for (int i = 0; i < records.length; i++)
+ {
+ int[] targetVersion = Util.parseVersionString(
+ (String) records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+
+ if (Util.compareVersion(targetVersion, version) == 0)
+ {
+ return records[i];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public BundleRecord[] resolvePackages(LocalState localState, Filter[] reqFilters)
+ throws ResolveException
+ {
+ // Create a list that will contain the transitive closure of
+ // all import dependencies; use a list because this will keep
+ // everything in order.
+ List deployList = new ArrayList();
+ // Add the target bundle
+ resolvePackages(localState, reqFilters, deployList);
+
+ // Convert list of symbolic names to an array of bundle
+ // records and return it.
+ BundleRecord[] records = new BundleRecord[deployList.size()];
+ return (BundleRecord[]) deployList.toArray(records);
+ }
+
+ private void resolvePackages(
+ LocalState localState, Filter[] reqFilters, List deployList)
+ throws ResolveException
+ {
+ for (int reqIdx = 0;
+ (reqFilters != null) && (reqIdx < reqFilters.length);
+ reqIdx++)
+ {
+ // If the package can be locally resolved, then
+ // it can be completely ignored; otherwise, try
+ // to find a resolving bundle.
+ if (!localState.isResolvable(reqFilters[reqIdx]))
+ {
+ // Select resolving bundle for current package.
+ BundleRecord source = selectResolvingBundle(
+ deployList, localState, reqFilters[reqIdx]);
+ // If there is no resolving bundle, then throw a
+ // resolve exception.
+ if (source == null)
+ {
+throw new IllegalArgumentException("HACK: SHOULD THROW RESOLVE EXCEPTION: " + reqFilters[reqIdx]);
+// throw new ResolveException(reqFilters[reqIdx]);
+ }
+ // If the resolving bundle is already in the deploy list,
+ // then just ignore it; otherwise, add it to the deploy
+ // list and resolve its packages.
+ if (!deployList.contains(source))
+ {
+ deployList.add(source);
+ Filter[] filters = (Filter[])
+ source.getAttribute("requirements");
+ resolvePackages(localState, filters, deployList);
+ }
+ }
+ }
+ }
+
+ /**
+ * Selects a single source bundle record for the target package from
+ * the repository. The algorithm tries to select a source bundle record
+ * if it is already installed locally in the framework; this approach
+ * favors updating already installed bundles rather than installing
+ * new ones. If no matching bundles are installed locally, then the
+ * first bundle record providing the target package is returned.
+ * @param targetPkg the target package for which to select a source
+ * bundle record.
+ * @return the selected bundle record or <tt>null</tt> if no sources
+ * could be found.
+ **/
+ private BundleRecord selectResolvingBundle(
+ List deployList, LocalState localState, Filter targetFilter)
+ {
+ BundleRecord[] exporters = findExporters(targetFilter);
+ if (exporters == null)
+ {
+ return null;
+ }
+
+ // Try to select a source bundle record that is already
+ // in the deployed list to minimize the number of bundles
+ // that need to be deployed. If this is not possible, then
+ // try to select a bundle that is already installed locally,
+ // since it might be possible to update this bundle to
+ // minimize the number of bundles installed in the framework.
+ for (int i = 0; i < exporters.length; i++)
+ {
+ if (deployList.contains(exporters[i]))
+ {
+ return exporters[i];
+ }
+ else
+ {
+ String symName = (String)
+ exporters[i].getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+ if (symName != null)
+ {
+ BundleRecord[] records = localState.findBundles(symName);
+ if (records != null)
+ {
+ return exporters[i];
+ }
+ }
+ }
+ }
+
+ // If none of the sources are installed locally, then
+ // just pick the first one.
+ return exporters[0];
+ }
+
+ /**
+ * Returns an array of bundle records that resolve the supplied
+ * package declaration.
+ * @param target the package declaration to resolve.
+ * @return an array of bundle records that resolve the package
+ * declaration or <tt>null</tt> if none are found.
+ **/
+ private BundleRecord[] findExporters(Filter targetFilter)
+ {
+ MapToDictionary mapDict = new MapToDictionary(null);
+
+ // Create a list for storing bundles that can resolve package.
+ List resolveList = new ArrayList();
+ for (int recIdx = 0; recIdx < m_recordArray.length; recIdx++)
+ {
+ Map[] capMaps = (Map[]) m_recordArray[recIdx].getAttribute("capability");
+ for (int capIdx = 0; capIdx < capMaps.length; capIdx++)
+ {
+ mapDict.setSourceMap(capMaps[capIdx]);
+ if (targetFilter.match(mapDict))
+ {
+ resolveList.add(m_recordArray[recIdx]);
+ }
+ }
+ }
+
+ // If no resolving bundles were found, return null.
+ if (resolveList.size() == 0)
+ {
+ return null;
+ }
+
+ // Otherwise, return an array containing resolving bundles.
+ return (BundleRecord[]) resolveList.toArray(new BundleRecord[resolveList.size()]);
+ }
+
+ private boolean isUpdateAvailable(
+ PrintStream out, PrintStream err, Bundle bundle)
+ {
+ // Get the bundle's update location.
+ String symname =
+ (String) bundle.getHeaders().get(BundleRecord.BUNDLE_SYMBOLICNAME);
+
+ // Get associated repository bundle recorded for the
+ // local bundle and see if an update is necessary.
+ BundleRecord[] records = getRecords(symname);
+ if (records == null)
+ {
+ err.println(Util.getBundleName(bundle) + " not in repository.");
+ return false;
+ }
+
+ // Check bundle version againts bundle record version.
+ for (int i = 0; i < records.length; i++)
+ {
+ int[] bundleVersion = Util.parseVersionString(
+ (String) bundle.getHeaders().get(BundleRecord.BUNDLE_VERSION));
+ int[] recordVersion = Util.parseVersionString(
+ (String) records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+ if (Util.compareVersion(recordVersion, bundleVersion) > 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void initialize()
+ {
+ m_initialized = true;
+ m_recordMap.clear();
+
+ for (int urlIdx = 0; (m_urls != null) && (urlIdx < m_urls.length); urlIdx++)
+ {
+ parseRepositoryFile(m_hopCount, m_urls[urlIdx]);
+ }
+
+ // Cache a sorted array of all bundle records.
+ List list = new ArrayList();
+ for (Iterator i = m_recordMap.entrySet().iterator(); i.hasNext(); )
+ {
+ BundleRecord[] records = (BundleRecord[]) ((Map.Entry) i.next()).getValue();
+ for (int recIdx = 0; recIdx < records.length; recIdx++)
+ {
+ list.add(records[recIdx]);
+ }
+ }
+ m_recordArray = (BundleRecord[]) list.toArray(new BundleRecord[list.size()]);
+ Arrays.sort(m_recordArray, new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ BundleRecord r1 = (BundleRecord) o1;
+ BundleRecord r2 = (BundleRecord) o2;
+ String name1 = (String) r1.getAttribute(BundleRecord.BUNDLE_NAME);
+ String name2 = (String) r2.getAttribute(BundleRecord.BUNDLE_NAME);
+ return name1.compareToIgnoreCase(name2);
+ }
+ });
+ }
+
+ private void parseRepositoryFile(int hopCount, String urlStr)
+ {
+ InputStream is = null;
+ BufferedReader br = null;
+
+ try
+ {
+ // Do it the manual way to have a chance to
+ // set request properties as proxy auth (EW).
+ URL url = new URL(urlStr);
+ URLConnection conn = url.openConnection();
+
+ // Support for http proxy authentication
+ String auth = System.getProperty("http.proxyAuth");
+ if ((auth != null) && (auth.length() > 0))
+ {
+ if ("http".equals(url.getProtocol()) ||
+ "https".equals(url.getProtocol()))
+ {
+ String base64 = Util.base64Encode(auth);
+ conn.setRequestProperty(
+ "Proxy-Authorization", "Basic " + base64);
+ }
+ }
+ is = conn.getInputStream();
+
+ // Create the parser Kxml
+ XmlCommonHandler handler = new XmlCommonHandler();
+ handler.addType("bundles", ArrayList.class);
+ handler.addType("repository", HashMap.class);
+ handler.addType("extern-repositories", ArrayList.class);
+ handler.addType("bundle", MultivalueMap.class);
+ handler.addType("requirement", String.class);
+ handler.addType("capability", ArrayList.class);
+ handler.addType("property", HashMap.class);
+ handler.setDefaultType(String.class);
+
+ br = new BufferedReader(new InputStreamReader(is));
+ KXmlSAXParser parser;
+ parser = new KXmlSAXParser(br);
+ try
+ {
+ parser.parseXML(handler);
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ return;
+ }
+
+ List root = (List) handler.getRoot();
+ for (int bundleIdx = 0; bundleIdx < root.size(); bundleIdx++)
+ {
+ Object obj = root.get(bundleIdx);
+
+ // The elements of the root will either be a HashMap for
+ // the repository tag or a MultivalueMap for the bundle
+ // tag, as indicated above when we parsed the file.
+
+ // If HashMap, then read repository information.
+ if (obj instanceof HashMap)
+ {
+ // Create a case-insensitive map.
+ Map repoMap = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ repoMap.putAll((Map) obj);
+
+ // Process external repositories if hop count is
+ // greater than zero.
+ if (hopCount > 0)
+ {
+ // Get the external repository list.
+ List externList = (List) repoMap.get(EXTERN_REPOSITORY_TAG);
+ for (int i = 0; (externList != null) && (i < externList.size()); i++)
+ {
+ parseRepositoryFile(hopCount - 1, (String) externList.get(i));
+ }
+ }
+ }
+ // Else if mulitvalue map, then create a bundle record
+ // for the associated bundle meta-data.
+ else if (obj instanceof MultivalueMap)
+ {
+ // Create a case-insensitive map.
+ Map bundleMap = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ bundleMap.putAll((Map) obj);
+
+ // Convert capabilities into case-insensitive maps.
+ List list = (List) bundleMap.get("capability");
+ Map[] capabilityMaps = convertCapabilities(list);
+ bundleMap.put("capability", capabilityMaps);
+
+ // Convert requirements info filters.
+ list = (List) bundleMap.get("requirement");
+ Filter[] filters = convertRequirements(list);
+ bundleMap.put("requirement", filters);
+
+ // Convert any remaining single-element lists into
+ // the element itself.
+ for (Iterator i = bundleMap.keySet().iterator(); i.hasNext(); )
+ {
+ Object key = i.next();
+ Object value = bundleMap.get(key);
+ if ((value instanceof List) &&
+ (((List) value).size() == 1))
+ {
+ bundleMap.put(key, ((List) value).get(0));
+ }
+ }
+
+ // Create a bundle record using the map.
+ BundleRecord record = new BundleRecord(bundleMap);
+ // TODO: Filter duplicates.
+ BundleRecord[] records =
+ (BundleRecord[]) m_recordMap.get(
+ record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME));
+ if (records == null)
+ {
+ records = new BundleRecord[] { record };
+ }
+ else
+ {
+ BundleRecord[] newRecords = new BundleRecord[records.length + 1];
+ System.arraycopy(records, 0, newRecords, 0, records.length);
+ newRecords[records.length] = record;
+ records = newRecords;
+ }
+ m_recordMap.put(
+ record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME), records);
+ }
+ }
+ }
+ catch (MalformedURLException ex)
+ {
+ ex.printStackTrace(System.err);
+// System.err.println("Error: " + ex);
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace(System.err);
+// System.err.println("Error: " + ex);
+ }
+ finally
+ {
+ try
+ {
+ if (is != null) is.close();
+ }
+ catch (IOException ex)
+ {
+ // Not much we can do.
+ }
+ }
+ }
+
+ private Map[] convertCapabilities(List capLists)
+ {
+ Map[] capabilityMaps = new Map[(capLists == null) ? 0 : capLists.size()];
+ for (int capIdx = 0; (capLists != null) && (capIdx < capLists.size()); capIdx++)
+ {
+ // Create a case-insensitive map.
+ capabilityMaps[capIdx] = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+
+ List capList = (List) capLists.get(capIdx);
+
+ for (int propIdx = 0; propIdx < capList.size(); propIdx++)
+ {
+ Map propMap = (Map) capList.get(propIdx);
+ String name = (String) propMap.get("name");
+ String type = (String) propMap.get("type");
+ String value = (String) propMap.get("value");
+ try
+ {
+ Class clazz = this.getClass().getClassLoader().loadClass(type);
+ Object o = clazz
+ .getConstructor(new Class[] { String.class })
+ .newInstance(new Object[] { value });
+ capabilityMaps[capIdx].put(name, o);
+ }
+ catch (Exception ex)
+ {
+// TODO: DETERMINE WHAT TO DO HERE.
+ // Two options here, we can either ignore the
+ // entire capability or we can just ignore the
+ // property. For now, just ignore the property.
+ continue;
+ }
+ }
+ }
+ return capabilityMaps;
+ }
+
+ private Filter[] convertRequirements(List reqsList)
+ {
+ Filter[] filters = new Filter[(reqsList == null) ? 0 : reqsList.size()];
+ for (int i = 0; (reqsList != null) && (i < reqsList.size()); i++)
+ {
+ try
+ {
+ filters[i] = m_context.createFilter((String) reqsList.get(i));
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ }
+ }
+ return filters;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/Util.java b/src/org/apache/osgi/bundle/bundlerepository/Util.java
new file mode 100644
index 0000000..7c40551
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/Util.java
@@ -0,0 +1,208 @@
+/*
+ * 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.StringTokenizer;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+public class Util
+{
+ public static String getBundleName(Bundle bundle)
+ {
+ String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+ return (name == null)
+ ? "Bundle " + Long.toString(bundle.getBundleId())
+ : name;
+ }
+
+ public static int compareVersion(int[] v1, int[] v2)
+ {
+ if (v1[0] > v2[0])
+ {
+ return 1;
+ }
+ else if (v1[0] < v2[0])
+ {
+ return -1;
+ }
+ else if (v1[1] > v2[1])
+ {
+ return 1;
+ }
+ else if (v1[1] < v2[1])
+ {
+ return -1;
+ }
+ else if (v1[2] > v2[2])
+ {
+ return 1;
+ }
+ else if (v1[2] < v2[2])
+ {
+ return -1;
+ }
+ return 0;
+ }
+
+ public static int[] parseVersionString(String s)
+ {
+ int[] version = new int[] { 0, 0, 0 };
+
+ if (s != null)
+ {
+ StringTokenizer st = new StringTokenizer(s, ".");
+ if (st.hasMoreTokens())
+ {
+ try
+ {
+ version[0] = Integer.parseInt(st.nextToken());
+ if (st.hasMoreTokens())
+ {
+ version[1] = Integer.parseInt(st.nextToken());
+ if (st.hasMoreTokens())
+ {
+ version[2] = Integer.parseInt(st.nextToken());
+ }
+ }
+ return version;
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new IllegalArgumentException(
+ "Improper version number.");
+ }
+ }
+ }
+
+ return version;
+ }
+
+ private static final byte encTab[] = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
+ 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52,
+ 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64,
+ 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70,
+ 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31,
+ 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f };
+
+ private static final byte decTab[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1,
+ -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1,
+ -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, -1, -1, -1, -1, -1 };
+
+ public static String base64Encode(String s) throws IOException
+ {
+ return encode(s.getBytes(), 0);
+ }
+
+ /**
+ * Encode a raw byte array to a Base64 String.
+ *
+ * @param in Byte array to encode.
+ * @param len Length of Base64 lines. 0 means no line breaks.
+ **/
+ public static String encode(byte[] in, int len) throws IOException
+ {
+ ByteArrayOutputStream baos = null;
+ ByteArrayInputStream bais = null;
+ try
+ {
+ baos = new ByteArrayOutputStream();
+ bais = new ByteArrayInputStream(in);
+ encode(bais, baos, len);
+ // ASCII byte array to String
+ return (new String(baos.toByteArray()));
+ }
+ finally
+ {
+ if (baos != null)
+ {
+ baos.close();
+ }
+ if (bais != null)
+ {
+ bais.close();
+ }
+ }
+ }
+
+ public static void encode(InputStream in, OutputStream out, int len)
+ throws IOException
+ {
+
+ // Check that length is a multiple of 4 bytes
+ if (len % 4 != 0)
+ {
+ throw new IllegalArgumentException("Length must be a multiple of 4");
+ }
+
+ // Read input stream until end of file
+ int bits = 0;
+ int nbits = 0;
+ int nbytes = 0;
+ int b;
+
+ while ((b = in.read()) != -1)
+ {
+ bits = (bits << 8) | b;
+ nbits += 8;
+ while (nbits >= 6)
+ {
+ nbits -= 6;
+ out.write(encTab[0x3f & (bits >> nbits)]);
+ nbytes++;
+ // New line
+ if (len != 0 && nbytes >= len)
+ {
+ out.write(0x0d);
+ out.write(0x0a);
+ nbytes -= len;
+ }
+ }
+ }
+
+ switch (nbits)
+ {
+ case 2:
+ out.write(encTab[0x3f & (bits << 4)]);
+ out.write(0x3d); // 0x3d = '='
+ out.write(0x3d);
+ break;
+ case 4:
+ out.write(encTab[0x3f & (bits << 2)]);
+ out.write(0x3d);
+ break;
+ }
+
+ if (len != 0)
+ {
+ if (nbytes != 0)
+ {
+ out.write(0x0d);
+ out.write(0x0a);
+ }
+ out.write(0x0d);
+ out.write(0x0a);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXHandler.java b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXHandler.java
new file mode 100644
index 0000000..b6a8e1d
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXHandler.java
@@ -0,0 +1,68 @@
+/*
+ * 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.kxmlsax;
+
+import java.util.Properties;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Interface for SAX handler with kXML
+ *
+ * @author Didier Donsez (didier.donsez@imag.fr)
+ */
+public interface KXmlSAXHandler {
+
+ /**
+ * Method called when parsing text
+ *
+ * @param ch
+ * @param offset
+ * @param length
+ * @exception SAXException
+ */
+ public void characters(char[] ch, int offset, int length) throws Exception;
+
+ /**
+ * Method called when a tag opens
+ *
+ * @param uri
+ * @param localName
+ * @param qName
+ * @param attrib
+ * @exception SAXException
+ **/
+ public void startElement(
+ String uri,
+ String localName,
+ String qName,
+ Properties attrib)
+ throws Exception;
+ /**
+ * Method called when a tag closes
+ *
+ * @param uri
+ * @param localName
+ * @param qName
+ * @exception SAXException
+ */
+ public void endElement(
+ java.lang.String uri,
+ java.lang.String localName,
+ java.lang.String qName)
+ throws Exception;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXParser.java b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXParser.java
new file mode 100644
index 0000000..1bb9b25
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXParser.java
@@ -0,0 +1,79 @@
+/*
+ * 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.kxmlsax;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Properties;
+
+import org.kxml.Attribute;
+import org.kxml.Xml;
+import org.kxml.parser.ParseEvent;
+import org.kxml.parser.XmlParser;
+
+/**
+ * The KXmlSAXParser extends the XmlParser from kxml. This is a very
+ * simple parser that does not take into account the DTD
+ *
+ * @version 1.0 08 Nov 2002
+ * @version 1.1 24 Apr 2004
+ * @author Humberto Cervantes, Didier Donsez
+ */
+public class KXmlSAXParser extends XmlParser {
+ /**
+ * The constructor for a parser, it receives a java.io.Reader.
+ *
+ * @param reader The reader
+ * @exception IOException thrown by the superclass
+ */
+ public KXmlSAXParser(Reader r) throws IOException {
+ super(r);
+ }
+
+ /**
+ * Parser from the reader provided in the constructor, and call
+ * the startElement and endElement in a KxmlHandler
+ *
+ * @param reader The reader
+ * @exception Exception thrown by the superclass
+ */
+ public void parseXML(KXmlSAXHandler handler) throws Exception {
+ ParseEvent evt = null;
+ do {
+ evt = read();
+ if (evt.getType() == Xml.START_TAG) {
+ Properties props = new Properties();
+ for (int i = 0; i < evt.getAttributeCount(); i++) {
+ Attribute attr = evt.getAttribute(i);
+ props.put(attr.getName(), attr.getValue());
+ }
+ handler.startElement(
+ "uri",
+ evt.getName(),
+ evt.getName(),
+ props);
+ } else if (evt.getType() == Xml.END_TAG) {
+ handler.endElement("uri", evt.getName(), evt.getName());
+ } else if (evt.getType() == Xml.TEXT) {
+ String text = evt.getText();
+ handler.characters(text.toCharArray(),0,text.length());
+ } else {
+ // do nothing
+ }
+ } while (evt.getType() != Xml.END_DOCUMENT);
+ }
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/manifest.mf b/src/org/apache/osgi/bundle/bundlerepository/manifest.mf
new file mode 100644
index 0000000..ad49efe
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/manifest.mf
@@ -0,0 +1,10 @@
+Bundle-Name: Bundle Repository
+Bundle-SymbolicName: org.apache.osgi.bundle.bundlerepository
+Bundle-Description: A simple bundle repository for Felix.
+Bundle-Activator: org.apache.osgi.bundle.bundlerepository.Activator
+Bundle-ClassPath: .,org/apache/osgi/bundle/bundlerepository/kxml.jar
+Bundle-Version: 2.0.0.alpha2
+Import-Package: org.osgi.framework
+DynamicImport-Package: org.apache.osgi.service.shell
+Export-Package:
+ org.apache.osgi.service.bundlerepository; specification-version="1.1.0"
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/ClassUtility.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/ClassUtility.java
new file mode 100644
index 0000000..fd9b6e9
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/ClassUtility.java
@@ -0,0 +1,97 @@
+/*
+ * 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.metadataparser;
+
+/**
+ * This class provides methods to process class name
+ */
+
+public class ClassUtility {
+
+ /**
+ * This method capitalizes the first character in the provided string.
+ * @return resulted string
+ */
+ public static String capitalize(String name) {
+
+ int len=name.length();
+ StringBuffer sb=new StringBuffer(len);
+ boolean setCap=true;
+ for(int i=0; i<len; i++){
+ char c=name.charAt(i);
+ if(c=='-' || c=='_') {
+ setCap=true;
+ } else {
+ if(setCap){
+ sb.append(Character.toUpperCase(c));
+ setCap=false;
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * This method capitalizes all characters in the provided string.
+ * @return resulted string
+ */
+ public static String finalstaticOf(String membername) {
+ int len=membername.length();
+ StringBuffer sb=new StringBuffer(len+2);
+ for(int i=0; i<len; i++){
+ char c=membername.charAt(i);
+ if(Character.isLowerCase(c) ) {
+ sb.append(Character.toUpperCase(c));
+ } else if(Character.isUpperCase(c) ) {
+ sb.append('_').append(c);
+ } else {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * This method returns the package name in a full class name
+ * @return resulted string
+ */
+ public static String packageOf(String fullclassname) {
+ int index=fullclassname.lastIndexOf(".");
+ if(index>0) {
+ return fullclassname.substring(0,index);
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * This method returns the package name in a full class name
+ * @return resulted string
+ */
+ public static String classOf(String fullclassname) {
+ int index=fullclassname.lastIndexOf(".");
+ if(index>0) {
+ return fullclassname.substring(index+1);
+ } else {
+ return fullclassname;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/KXmlMetadataHandler.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/KXmlMetadataHandler.java
new file mode 100644
index 0000000..077a551
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/KXmlMetadataHandler.java
@@ -0,0 +1,63 @@
+/*
+ * 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.metadataparser;
+
+import java.io.*;
+
+import org.apache.osgi.bundle.bundlerepository.kxmlsax.KXmlSAXParser;
+
+
+/**
+ * handles the metadata in XML format
+ * (use kXML (http://kxml.enhydra.org/) a open-source very light weight XML parser
+ * @version 1.00 11 Nov 2003
+ * @author Didier Donsez
+ */
+public class KXmlMetadataHandler /*implements MetadataHandler*/ {
+
+ private XmlCommonHandler handler;
+
+ public KXmlMetadataHandler() {
+ handler = new XmlCommonHandler();
+ }
+
+ /**
+ * Called to parse the InputStream and set bundle list and package hash map
+ */
+ public void parse(InputStream is) throws Exception {
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+ KXmlSAXParser parser;
+ parser = new KXmlSAXParser(br);
+ parser.parseXML(handler);
+ }
+
+ /**
+ * return the metadata
+ * @return a Objet
+ */
+ public Object getMetadata() {
+ return handler.getRoot();
+ }
+
+ public void addType(String qname, Class clazz) {
+ handler.addType(qname, clazz);
+ }
+
+ public void setDefaultType(Class clazz) {
+ handler.setDefaultType(clazz);
+ }
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/MultivalueMap.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/MultivalueMap.java
new file mode 100644
index 0000000..ba29423
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/MultivalueMap.java
@@ -0,0 +1,142 @@
+/*
+ * 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.metadataparser;
+
+import java.util.*;
+
+public class MultivalueMap implements Map {
+ private Map m_map = null;
+
+ public MultivalueMap() {
+ m_map = new HashMap();
+ }
+
+ public MultivalueMap(Map map) {
+ m_map = map;
+ }
+
+ /**
+ * @see java.util.Map#size()
+ */
+ public int size() {
+ return m_map.size();
+ }
+
+ /**
+ * @see java.util.Map#clear()
+ */
+ public void clear() {
+ m_map.clear();
+ }
+
+ /**
+ * @see java.util.Map#isEmpty()
+ */
+ public boolean isEmpty() {
+ return m_map.isEmpty();
+ }
+
+ /**
+ * @see java.util.Map#containsKey(java.lang.Object)
+ */
+ public boolean containsKey(Object arg0) {
+ return m_map.containsKey(arg0);
+ }
+
+ /**
+ * @see java.util.Map#containsValue(java.lang.Object)
+ */
+ public boolean containsValue(Object arg0) {
+ return false;
+ }
+
+ /**
+ * @see java.util.Map#values()
+ */
+ public Collection values() {
+ return null;
+ }
+
+ /**
+ * @see java.util.Map#putAll(java.util.Map)
+ */
+ public void putAll(Map arg0) {
+ }
+
+ /**
+ * @see java.util.Map#entrySet()
+ */
+ public Set entrySet() {
+ return m_map.entrySet();
+ }
+
+ /**
+ * @see java.util.Map#keySet()
+ */
+ public Set keySet() {
+ return m_map.keySet();
+ }
+
+ /**
+ * @see java.util.Map#get(java.lang.Object)
+ */
+ public Object get(Object key) {
+ return m_map.get(key);
+ }
+
+ /**
+ * @see java.util.Map#remove(java.lang.Object)
+ */
+ public Object remove(Object arg0) {
+ return m_map.remove(arg0);
+ }
+
+ /**
+ * @see java.util.Map#put(java.lang.Object, java.lang.Object)
+ */
+ public Object put(Object key, Object value) {
+ Object prev = m_map.get(key);
+ if (prev == null) {
+ List list = new ArrayList();
+ list.add(value);
+ m_map.put(key, list);
+ return list;
+ } else {
+ ((List) prev).add(value);
+ return prev;
+ }
+ }
+
+ public String toString() {
+ StringBuffer sb=new StringBuffer();
+ sb.append("[MultivalueMap:");
+ if(m_map.isEmpty()) {
+ sb.append("empty");
+ } else {
+ Set keys=m_map.keySet();
+ Iterator iter=keys.iterator();
+ while(iter.hasNext()){
+ String key=(String)iter.next();
+ sb.append("\n\"").append(key).append("\":");
+ sb.append(m_map.get(key).toString());
+ }
+ sb.append('\n');
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/XmlCommonHandler.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/XmlCommonHandler.java
new file mode 100644
index 0000000..628b4c6
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/XmlCommonHandler.java
@@ -0,0 +1,405 @@
+/*
+ * 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.metadataparser;
+
+import java.lang.reflect.Method;
+import java.util.*;
+
+import org.apache.osgi.bundle.bundlerepository.kxmlsax.KXmlSAXHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * SAX handler for the XML OBR file
+ *
+ * @author Didier Donsez (didier.donsez@imag.fr)
+ */
+public class XmlCommonHandler implements KXmlSAXHandler {
+
+ private static final String PI_MAPPING="mapping";
+
+ private int columnNumber;
+
+ private int lineNumber;
+
+ //
+ // Data
+ //
+
+ private Object root;
+
+ private Stack objectStack;
+ private Stack qnameStack;
+
+ private Map types;
+ private Class defaultType;
+
+ private StringBuffer currentText;
+
+ public XmlCommonHandler() {
+ objectStack = new Stack();
+ qnameStack = new Stack();
+ types = new HashMap();
+ }
+
+ public void addType(String qname, Class clazz) {
+ types.put(qname, clazz);
+ }
+
+ public void setDefaultType(Class clazz) {
+ defaultType=clazz;
+ }
+
+ public Object getRoot() {
+ return root;
+ }
+
+ /* for PCDATA */
+ public void characters(char[] ch, int offset, int length)
+ throws Exception {
+ if (currentText != null)
+ currentText.append(ch, offset, length);
+ }
+
+ private String adderOf(Class clazz) {
+ return "add"
+ + ClassUtility.capitalize(ClassUtility.classOf(clazz.getName()));
+ }
+
+ private String adderOf(String key) {
+ return "add" + ClassUtility.capitalize(key);
+ }
+
+ private String setterOf(Class clazz) {
+ return "set"
+ + ClassUtility.capitalize(ClassUtility.classOf(clazz.getName()));
+ }
+
+ private String setterOf(String key) {
+ return "set" + ClassUtility.capitalize(key);
+ }
+
+ /**
+ * Method called when a tag opens
+ *
+ * @param uri
+ * @param localName
+ * @param qName
+ * @param attrib
+ * @exception SAXException
+ **/
+ public void startElement(
+ String uri,
+ String localName,
+ String qName,
+ Properties attrib)
+ throws Exception {
+
+ trace("START ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+
+ Class clazz = (Class) types.get(qName);
+ // TODO: should add uri in the future
+
+ if(clazz==null && defaultType!=null)
+ clazz=defaultType;
+
+ Object obj;
+ if (clazz != null) {
+
+ try {
+ obj = clazz.newInstance();
+ } catch (InstantiationException e) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "class "+clazz.getName()+" for element " + qName + " should have an empty constructor");
+ } catch (IllegalAccessException e) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "illegal access on the empty constructor of class "+clazz.getName()+" for element " + qName);
+ }
+
+ Set keyset = attrib.keySet();
+ Iterator iter = keyset.iterator();
+ while (iter.hasNext()) {
+ String key = (String) iter.next();
+
+ if (obj instanceof Map) {
+ ((Map) obj).put(key, attrib.get(key));
+ } else if (obj instanceof List) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "List element " + qName + " cannot have any attribute");
+ } else if (obj instanceof String) {
+ if(key.equals("value")){
+ obj=(String)attrib.get(key);
+ } else {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "String element " + qName + " cannot have other attribute than value");
+ }
+ } else {
+ Method method = null;
+ try {
+ method =
+ clazz.getMethod(
+ setterOf(key),
+ new Class[] { String.class });
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method =
+ clazz.getMethod(
+ adderOf(key),
+ new Class[] { String.class });
+
+ } catch (NoSuchMethodException e) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "element "
+ + qName
+ + " does not support the attribute "
+ + key);
+ }
+ if (method != null)
+ method.invoke(
+ obj,
+ new String[] {(String) attrib.get(key)});
+ }
+
+ }
+
+ } else {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "this element " + qName + " has not corresponding class");
+ }
+
+ if (root == null)
+ root = obj;
+ objectStack.push(obj);
+ qnameStack.push(qName);
+ currentText = new StringBuffer();
+
+ trace("START/ ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+ }
+
+ /**
+ * Method called when a tag closes
+ *
+ * @param uri
+ * @param localName
+ * @param qName
+ * @exception SAXException
+ */
+ public void endElement(
+ java.lang.String uri,
+ java.lang.String localName,
+ java.lang.String qName)
+ throws Exception {
+
+ trace("END ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+
+ Object obj = objectStack.pop();
+
+ if (currentText != null && currentText.length() != 0) {
+ if (obj instanceof Map) {
+ ((Map) obj).put(qName, currentText.toString().trim());
+ } else if (obj instanceof List) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "List element " + qName + " cannot have PCDATAs");
+ } else if (obj instanceof String) {
+ String str=(String)obj;
+ if(str.length()!=0){
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "String element " + qName + " cannot have both PCDATA and an attribute value");
+ } else {
+ obj=currentText.toString().trim();
+ }
+ } else {
+ Method method = null;
+ try {
+ method =
+ obj.getClass().getMethod(
+ "addText",
+ new Class[] { String.class });
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ }
+ if (method != null) {
+ method.invoke(obj, new String[] { currentText.toString().trim()});
+ }
+ }
+ }
+
+ currentText = null;
+
+ if (!objectStack.isEmpty()) {
+
+ Object parent = objectStack.peek();
+ String parentName = (String) qnameStack.peek();
+
+ if (parent instanceof Map) {
+ ((Map) parent).put(qName, obj);
+ } else if (parent instanceof List) {
+ ((List) parent).add(obj);
+ } else {
+ Method method = null;
+ try {
+ method =
+ parent.getClass().getMethod(
+ adderOf(ClassUtility.capitalize(qName)),
+ new Class[] { obj.getClass()});
+ } catch (NoSuchMethodException e) {
+ trace(
+ "NoSuchMethodException: "
+ + adderOf(ClassUtility.capitalize(qName)));
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method =
+ parent.getClass().getMethod(
+ setterOf(ClassUtility.capitalize(qName)),
+ new Class[] { obj.getClass()});
+ } catch (NoSuchMethodException e) {
+ trace(
+ "NoSuchMethodException: "
+ + setterOf(ClassUtility.capitalize(qName)));
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method =
+ parent.getClass().getMethod(
+ adderOf(obj.getClass()),
+ new Class[] { obj.getClass()});
+ } catch (NoSuchMethodException e) {
+ trace(
+ "NoSuchMethodException: "
+ + adderOf(obj.getClass()));
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method =
+ parent.getClass().getMethod(
+ setterOf(obj.getClass()),
+ new Class[] { obj.getClass()});
+ } catch (NoSuchMethodException e) {
+ trace(
+ "NoSuchMethodException: "
+ + setterOf(obj.getClass()));
+ // do nothing
+ }
+
+ if (method != null) {
+ trace(method.getName());
+ method.invoke(parent, new Object[] { obj });
+ } else {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " element " + parentName + " cannot have an attribute " + qName + " of type " + obj.getClass());
+ }
+ }
+
+ }
+
+ trace("END/ ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+
+ }
+
+ private void trace(String msg) {
+ if (false)
+ System.err.println(msg);
+ }
+
+ /**
+ * @see kxml.sax.KXmlSAXHandler#setLineNumber(int)
+ */
+ public void setLineNumber(int lineNumber) {
+ this.lineNumber=lineNumber;
+ }
+
+ /**
+ * @see kxml.sax.KXmlSAXHandler#setColumnNumber(int)
+ */
+ public void setColumnNumber(int columnNumber) {
+ this.columnNumber=columnNumber;
+
+ }
+
+ /**
+ * @see kxml.sax.KXmlSAXHandler#processingInstruction(java.lang.String, java.lang.String)
+ */
+ public void processingInstruction(String target, String data) throws Exception {
+ trace("pi:"+target+";"+data);
+ if(target==null){ // TODO kXML
+ if(!data.startsWith(PI_MAPPING)) return;
+ } else if(!target.equals(PI_MAPPING))return;
+
+
+ // defaultclass attribute
+ String datt="defaultclass=\"";
+ int dstart=data.indexOf(datt);
+ if(dstart!=-1) {
+ int dend=data.indexOf("\"",dstart+datt.length());
+ if(dend==-1)
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " \"defaultclass\" attribute in \"mapping\" PI is not quoted");
+
+ String classname=data.substring(dstart+datt.length(),dend);
+ Class clazz=null;
+ try {
+ clazz=getClass().getClassLoader().loadClass(classname);
+ } catch (ClassNotFoundException e) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " cannot found class "+ classname+" for \"mapping\" PI");
+ }
+ setDefaultType(clazz);
+ return;
+ }
+
+ // element attribute
+ String eatt="element=\"";
+ int estart=data.indexOf(eatt);
+ if(estart==-1)
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " missing \"element\" attribute in \"mapping\" PI");
+ int eend=data.indexOf("\"",estart+eatt.length());
+ if(eend==-1)
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " \"element\" attribute in \"mapping\" PI is not quoted");
+
+ String element=data.substring(estart+eatt.length(),eend);
+
+ // element class
+ String catt="class=\"";
+ int cstart=data.indexOf(catt);
+ if(cstart==-1)
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " missing \"class\" attribute in \"mapping\" PI");
+ int cend=data.indexOf("\"",cstart+catt.length());
+ if(cend==-1)
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " \"class\" attribute in \"mapping\" PI is not quoted");
+
+ String classname=data.substring(cstart+catt.length(),cend);
+
+ Class clazz=null;
+ try {
+ clazz=getClass().getClassLoader().loadClass(classname);
+ } catch (ClassNotFoundException e) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " cannot found class "+ classname+" for \"mapping\" PI");
+ }
+ addType(element,clazz);
+ }
+}