You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ch...@apache.org on 2008/08/02 11:56:04 UTC

svn commit: r681945 [1/9] - in /felix/trunk/org.osgi.compendium: ./ doc/ src/main/java/info/ src/main/java/info/dmtree/ src/main/java/info/dmtree/notification/ src/main/java/info/dmtree/notification/spi/ src/main/java/info/dmtree/registry/ src/main/jav...

Author: christian
Date: Sat Aug  2 02:56:01 2008
New Revision: 681945

URL: http://svn.apache.org/viewvc?rev=681945&view=rev
Log:
FELIX-514 Updated compendium bundle to R4.1 OSGi API

Added:
    felix/trunk/org.osgi.compendium/src/main/java/info/
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/Acl.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtAdmin.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtEvent.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtEventListener.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtException.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtIllegalStateException.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtSession.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/MetaNode.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/Uri.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/notification/
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/notification/AlertItem.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/notification/NotificationService.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/notification/spi/
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/notification/spi/RemoteAlertSender.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/registry/
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/registry/DmtServiceFactory.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/security/
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/security/AlertPermission.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/security/DmtPermission.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/security/DmtPrincipalPermission.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/spi/
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/spi/DataPlugin.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/spi/ExecPlugin.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/spi/ReadWriteDataSession.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/spi/ReadableDataSession.java
    felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/spi/TransactionalDataSession.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/application/
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/application/ApplicationContext.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/application/ApplicationServiceEvent.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/application/ApplicationServiceListener.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/application/Framework.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/application/
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/application/ApplicationAdminPermission.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/application/ApplicationDescriptor.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/application/ApplicationException.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/application/ApplicationHandle.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/application/ScheduledApplication.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/BundleInfo.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/DeploymentAdmin.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/DeploymentAdminPermission.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/DeploymentException.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/DeploymentPackage.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/spi/
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/spi/DeploymentCustomizerPermission.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/spi/DeploymentSession.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/spi/ResourceProcessor.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/deploymentadmin/spi/ResourceProcessorException.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/monitor/
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/monitor/MonitorAdmin.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/monitor/MonitorListener.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/monitor/MonitorPermission.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/monitor/Monitorable.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/monitor/MonitoringJob.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/monitor/StatusVariable.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/util/gsm/
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/util/gsm/IMEICondition.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/util/gsm/IMSICondition.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/util/mobile/
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/util/mobile/UserPromptCondition.java
    felix/trunk/org.osgi.compendium/src/main/resources/info/
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/notification/
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/notification/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/notification/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/notification/spi/
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/notification/spi/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/notification/spi/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/registry/
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/registry/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/registry/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/security/
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/security/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/security/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/spi/
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/spi/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/info/dmtree/spi/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/application/
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/application/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/application/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/application/
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/application/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/application/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/deploymentadmin/
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/deploymentadmin/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/deploymentadmin/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/deploymentadmin/spi/
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/deploymentadmin/spi/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/deploymentadmin/spi/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/monitor/
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/monitor/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/service/monitor/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/util/gsm/
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/util/gsm/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/util/gsm/packageinfo
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/util/mobile/
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/util/mobile/package.html
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/util/mobile/packageinfo
    felix/trunk/org.osgi.compendium/xmlns/app/
    felix/trunk/org.osgi.compendium/xmlns/app/app.xsd
Modified:
    felix/trunk/org.osgi.compendium/doc/changelog.txt
    felix/trunk/org.osgi.compendium/pom.xml
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/cm/ConfigurationAdmin.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/service/useradmin/Authorization.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/util/tracker/ServiceTracker.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/util/tracker/ServiceTrackerCustomizer.java
    felix/trunk/org.osgi.compendium/src/main/java/org/osgi/util/xml/XMLParserActivator.java
    felix/trunk/org.osgi.compendium/src/main/resources/about.html
    felix/trunk/org.osgi.compendium/src/main/resources/org/osgi/util/tracker/packageinfo
    felix/trunk/org.osgi.compendium/xmlns/scr/scr.xsd

Modified: felix/trunk/org.osgi.compendium/doc/changelog.txt
URL: http://svn.apache.org/viewvc/felix/trunk/org.osgi.compendium/doc/changelog.txt?rev=681945&r1=681944&r2=681945&view=diff
==============================================================================
--- felix/trunk/org.osgi.compendium/doc/changelog.txt (original)
+++ felix/trunk/org.osgi.compendium/doc/changelog.txt Sat Aug  2 02:56:01 2008
@@ -1,3 +1,5 @@
+* [2008-08-02] Updated from Compendium 4.0 to 4.1 (FELIX-514)
+
 Changes from 1.0.0 to 1.0.1
 -------------------------------------
 

Modified: felix/trunk/org.osgi.compendium/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/org.osgi.compendium/pom.xml?rev=681945&r1=681944&r2=681945&view=diff
==============================================================================
--- felix/trunk/org.osgi.compendium/pom.xml (original)
+++ felix/trunk/org.osgi.compendium/pom.xml Sat Aug  2 02:56:01 2008
@@ -27,10 +27,10 @@
     <url>http://www.osgi.org/</url>
   </organization>
   <modelVersion>4.0.0</modelVersion>
-  <name>OSGi R4 Compendium Bundle</name>
   <description>OSGi Service Platform Release 4 Compendium Interfaces and Classes.</description>
   <artifactId>org.osgi.compendium</artifactId>
   <version>1.1.0-SNAPSHOT</version>
+  <name>OSGi R4 Compendium Bundle</name>
   <packaging>bundle</packaging>
   <dependencies>
     <dependency>
@@ -58,13 +58,13 @@
         <extensions>true</extensions>
         <configuration>
           <instructions>
+            <Bundle-Version>4.1.0</Bundle-Version>
             <Bundle-SymbolicName>org.osgi.compendium</Bundle-SymbolicName>
-            <Export-Package>org.osgi.service.cm,org.osgi.service.component,org.osgi.service.device,org.osgi.service.event,org.osgi.service.http,org.osgi.service.io,org.osgi.service.log,org.osgi.service.metatype,org.osgi.service.prefs,org.osgi.service.provisioning,org.osgi.service.upnp,org.osgi.service.useradmin,org.osgi.service.wireadmin,org.osgi.util.measurement,org.osgi.util.position,org.osgi.util.tracker,org.osgi.util.xml</Export-Package>
+            <Bundle-Copyright>Copyright (c) OSGi Alliance (2000, 2007). All Rights Reserved.</Bundle-Copyright>
+            <Bundle-Category>osgi</Bundle-Category>
+            <Export-Package>info.dmtree.notification,info.dmtree.notification.spi,info.dmtree.registry,info.dmtree.security,info.dmtree.spi,org.osgi.service.application,org.osgi.service.cm,org.osgi.service.component,org.osgi.service.deploymentadmin,org.osgi.service.deploymentadmin.spi,org.osgi.service.device,org.osgi.service.event,org.osgi.service.http,org.osgi.service.io,org.osgi.service.log,org.osgi.service.metatype,org.osgi.service.monitor,org.osgi.service.prefs,org.osgi.service.provisioning,org.osgi.service.upnp,org.osgi.service.useradmin,org.osgi.service.wireadmin,org.osgi.util.gsm,org.osgi.util.measurement,org.osgi.util.mobile,org.osgi.util.position,org.osgi.util.tracker,org.osgi.util.xml</Export-Package>
             <Import-Package />
             <DynamicImport-Package>*</DynamicImport-Package>
-            <Bundle-Version>4</Bundle-Version>
-            <Bundle-Copyright>Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.</Bundle-Copyright>
-            <Bundle-Category>osgi</Bundle-Category>
           </instructions>
         </configuration>
       </plugin>

Added: felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/Acl.java
URL: http://svn.apache.org/viewvc/felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/Acl.java?rev=681945&view=auto
==============================================================================
--- felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/Acl.java (added)
+++ felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/Acl.java Sat Aug  2 02:56:01 2008
@@ -0,0 +1,576 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/Acl.java,v 1.6 2006/07/12 21:21:37 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ * 
+ * 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 info.dmtree;
+
+import java.util.*;
+
+/**
+ * <code>Acl</code> is an immutable class representing structured access to
+ * DMT ACLs. Under OMA DM the ACLs are defined as strings with an internal
+ * syntax.
+ * <p>
+ * The methods of this class taking a principal as parameter accept remote
+ * server IDs (as passed to
+ * {@link DmtAdmin#getSession(String, String, int) DmtAdmin.getSession}), as
+ * well as &quot; <code>*</code> &quot; indicating any principal.
+ * <p>
+ * The syntax for valid remote server IDs:<br>
+ * &lt;<i>server-identifier</i>&gt; ::= All printable characters except 
+ *   <code>'='</code>, <code>'&amp;'</code>, <code>'*'</code>, <code>'+'</code>
+ *    or white-space characters.
+ */
+public final class Acl {
+
+    // ----- Public constants -----//
+
+    /**
+     * Principals holding this permission can issue GET command on the node
+     * having this ACL.
+     */
+    public static final int GET = 1;
+
+    /**
+     * Principals holding this permission can issue ADD commands on the node
+     * having this ACL.
+     */
+    public static final int ADD = 2;
+
+    /**
+     * Principals holding this permission can issue REPLACE commands on the node
+     * having this ACL.
+     */
+    public static final int REPLACE = 4;
+
+    /**
+     * Principals holding this permission can issue DELETE commands on the node
+     * having this ACL.
+     */
+    public static final int DELETE = 8;
+
+    /**
+     * Principals holding this permission can issue EXEC commands on the node
+     * having this ACL.
+     */
+    public static final int EXEC = 16;
+
+    /**
+     * Principals holding this permission can issue any command on the node
+     * having this ACL. This permission is the logical OR of {@link #ADD},
+     * {@link #DELETE}, {@link #EXEC}, {@link #GET} and {@link #REPLACE}
+     * permissions.
+     */
+    public static final int ALL_PERMISSION = ADD | DELETE | EXEC | GET
+            | REPLACE;
+
+    // ----- Private constants -----//
+
+    private static final int[] PERMISSION_CODES = new int[] { ADD, DELETE,
+            EXEC, GET, REPLACE };
+
+    private static final String[] PERMISSION_NAMES = new String[] { "Add",
+            "Delete", "Exec", "Get", "Replace" };
+
+    private static final String ALL_PRINCIPALS = "*";
+
+    // ----- Private fields -----//
+
+    // the implementation takes advantage of this being a sorted map
+    private final TreeMap principalPermissions;
+
+    private final int globalPermissions;
+
+    // ----- Public constructors -----//
+
+    /**
+     * Create an instance of the ACL from its canonic string representation.
+     * 
+     * @param acl The string representation of the ACL as defined in OMA DM. If
+     *        <code>null</code> or empty then it represents an empty list of
+     *        principals with no permissions.
+     * @throws IllegalArgumentException if acl is not a valid OMA DM ACL string
+     */
+    public Acl(String acl) {
+        if (acl == null || acl.equals("")) { // empty permission set
+            principalPermissions = new TreeMap();
+            globalPermissions = 0;
+            return;
+        }
+
+        TreeMap tempPrincipalPermissions = new TreeMap();
+        int tempGlobalPermissions = 0;
+
+        String[] aclEntries = split(acl, '&', -1);
+        for (int i = 0; i < aclEntries.length; i++) {
+            if (aclEntries[i].length() == 0)
+                throw new IllegalArgumentException(
+                        "Invalid ACL string: empty ACL entry.");
+
+            String[] entryParts = split(aclEntries[i], '=', 2);
+            if (entryParts.length == 1)
+                throw new IllegalArgumentException(
+                        "Invalid ACL string: no '=' in ACL entry.");
+            if (entryParts[1].length() == 0)
+                throw new IllegalArgumentException(
+                        "Invalid ACL string: no server identifiers in ACL entry.");
+
+            int command = parseCommand(entryParts[0]);
+            String[] serverIds = split(entryParts[1], '+', -1);
+            for (int j = 0; j < serverIds.length; j++) {
+                if (serverIds[j].length() == 0)
+                    throw new IllegalArgumentException(
+                            "Invalid ACL string: empty server identifier.");
+
+                if (serverIds[j].equals(ALL_PRINCIPALS))
+                    tempGlobalPermissions |= command;
+                else {
+                    checkServerId(serverIds[j], "Invalid ACL string: "
+                            + "server ID contains illegal character");
+                    Integer n = (Integer) tempPrincipalPermissions
+                            .get(serverIds[j]);
+                    int oldPermission = (n != null) ? n.intValue() : 0;
+                    tempPrincipalPermissions.put(serverIds[j], new Integer(
+                            oldPermission | command));
+                }
+            }
+        }
+
+        principalPermissions = tempPrincipalPermissions;
+        globalPermissions = tempGlobalPermissions;
+    }
+
+    /**
+     * Creates an instance with a specified list of principals and the
+     * permissions they hold. The two arrays run in parallel, that is
+     * <code>principals[i]</code> will hold <code>permissions[i]</code> in
+     * the ACL.
+     * <p>
+     * A principal name may not appear multiple times in the 'principals'
+     * argument. If the &quot;*&quot; principal appears in the array, the
+     * corresponding permissions will be granted to all principals (regardless
+     * of whether they appear in the array or not).
+     * 
+     * @param principals The array of principals
+     * @param permissions The array of permissions
+     * @throws IllegalArgumentException if the length of the two arrays are not
+     *         the same, if any array element is invalid, or if a principal
+     *         appears multiple times in the <code>principals</code> array
+     */
+    public Acl(String[] principals, int[] permissions) {
+        if (principals.length != permissions.length)
+            throw new IllegalArgumentException(
+                    "The lengths of the principal and permission arrays are not the same.");
+
+        TreeMap tempPrincipalPermissions = new TreeMap();
+        int tempGlobalPermissions = 0;
+
+        for (int i = 0; i < principals.length; i++) {
+            // allow one * in 'principals' array, remove after loop
+            if (!ALL_PRINCIPALS.equals(principals[i]))
+                checkPrincipal(principals[i]);
+            checkPermissions(permissions[i]);
+
+            Integer permInt = new Integer(permissions[i]);
+            Object old = tempPrincipalPermissions.put(principals[i], permInt);
+            if (old != null)
+                throw new IllegalArgumentException("Principal '"
+                        + principals[i]
+                        + "' appears multiple times in the principal array.");
+        }
+
+        // set the global permissions if there was a * in the array
+        Object globalPermObj = tempPrincipalPermissions.remove(ALL_PRINCIPALS);
+        if (globalPermObj != null)
+            tempGlobalPermissions = ((Integer) globalPermObj).intValue();
+
+        principalPermissions = tempPrincipalPermissions;
+        globalPermissions = tempGlobalPermissions;
+    }
+
+    // ----- Private constructors -----//
+
+    /**
+     * Creates an instance identical to the <code>base</code> ACL except for
+     * the permissions of the given <code>principal</code>, which are
+     * overwritten with the given <code>permissions</code>.
+     * <p>
+     * Assumes that the permissions parameter has been checked. All
+     * modifications of an <code>Acl</code> (add, delete, set) are done
+     * through this method.
+     * 
+     * @param base The ACL that provides all permissions except for permissions
+     *        of the given principal.
+     * @param principal The entity to which permission should be granted.
+     * @param permissions The set of permissions to be given. The parameter can
+     *        be a logical <code>or</code> of the permission constants defined
+     *        in this class.
+     */
+    private Acl(Acl base, String principal, int permissions) {
+        // make a shallow copy of the permission table, the keys (String) and
+        // values (Integer) are immutable anyway
+        TreeMap tempPrincipalPermissions = (TreeMap) base.principalPermissions
+                .clone();
+        int tempGlobalPermissions = base.globalPermissions;
+
+        int deletedGlobalPerm = tempGlobalPermissions & ~permissions;
+        if (ALL_PRINCIPALS.equals(principal)) {
+            deleteFromAll(tempPrincipalPermissions, deletedGlobalPerm);
+            tempGlobalPermissions = permissions;
+        } else {
+            checkPrincipal(principal);
+
+            if (deletedGlobalPerm != 0)
+                throw new IllegalArgumentException(
+                        "Cannot revoke globally set permissions ("
+                                + writeCommands(deletedGlobalPerm)
+                                + ") from a specific principal (" + principal
+                                + ").");
+
+            setPrincipalPermission(tempPrincipalPermissions, principal,
+                    permissions);
+        }
+
+        principalPermissions = tempPrincipalPermissions;
+        globalPermissions = tempGlobalPermissions;
+    }
+
+    // ----- Public methods -----//
+
+    /**
+     * Checks whether the given object is equal to this <code>Acl</code>
+     * instance. Two <code>Acl</code> instances are equal if they allow the
+     * same set of permissions for the same set of principals.
+     * 
+     * @param obj the object to compare with this <code>Acl</code> instance
+     * @return <code>true</code> if the parameter represents the same ACL as
+     *         this instance
+     */
+    public boolean equals(Object obj) {
+        if (obj == this)
+            return true;
+
+        if (!(obj instanceof Acl))
+            return false;
+
+        Acl other = (Acl) obj;
+
+        if (globalPermissions != other.globalPermissions
+                || principalPermissions.size() != other.principalPermissions
+                        .size())
+            return false;
+
+        // principalPermissions sets cannot be easily compared, because they are
+        // not canonical: the global permissions may or may not be present for
+        // each principal, without changing the meaning of the Acl object.
+
+        // Compare canonical string representations, inefficient but simple.
+        return toString().equals(other.toString());
+    }
+
+    /**
+     * Returns the hash code for this ACL instance. If two <code>Acl</code>
+     * instances are equal according to the {@link #equals} method, then calling
+     * this method on each of them must produce the same integer result.
+     * 
+     * @return hash code for this ACL
+     */
+    public int hashcode() {
+        // Using the hash code of the canonical string representation, because
+        // the principalPermissions set is not canonical (see above).
+        return toString().hashCode();
+    }
+
+    /**
+     * Create a new <code>Acl</code> instance from this <code>Acl</code> with 
+     * the given permission added for the given principal. The already existing
+     * permissions of the principal are not affected.
+     * 
+     * @param principal The entity to which permissions should be granted, or
+     *        &quot;*&quot; to grant permissions to all principals.
+     * @param permissions The permissions to be given. The parameter can be a
+     *        logical <code>or</code> of more permission constants defined in
+     *        this class.
+     * @return a new <code>Acl</code> instance
+     * @throws IllegalArgumentException if <code>principal</code> is not a
+     *         valid principal name or if <code>permissions</code> is not a
+     *         valid combination of the permission constants defined in this
+     *         class
+     */
+    public synchronized Acl addPermission(String principal, int permissions) {
+        checkPermissions(permissions);
+
+        int oldPermissions = getPermissions(principal);
+        return setPermission(principal, oldPermissions | permissions);
+    }
+
+    /**
+     * Create a new <code>Acl</code> instance from this <code>Acl</code> with 
+     * the given permission revoked from the given principal. Other permissions 
+     * of the principal are not affected.
+     * <p>
+     * Note, that it is not valid to revoke a permission from a specific
+     * principal if that permission is granted globally to all principals.
+     * 
+     * @param principal The entity from which permissions should be revoked, or
+     *        &quot;*&quot; to revoke permissions from all principals.
+     * @param permissions The permissions to be revoked. The parameter can be a
+     *        logical <code>or</code> of more permission constants defined in
+     *        this class.
+     * @return a new <code>Acl</code> instance
+     * @throws IllegalArgumentException if <code>principal</code> is not a
+     *         valid principal name, if <code>permissions</code> is not a
+     *         valid combination of the permission constants defined in this
+     *         class, or if a globally granted permission would have been
+     *         revoked from a specific principal
+     */
+    public synchronized Acl deletePermission(String principal, int permissions) {
+        checkPermissions(permissions);
+
+        int oldPermissions = getPermissions(principal);
+        return setPermission(principal, oldPermissions & ~permissions);
+    }
+
+    /**
+     * Get the permissions associated to a given principal.
+     * 
+     * @param principal The entity whose permissions to query, or &quot;*&quot;
+     *        to query the permissions that are granted globally, to all
+     *        principals
+     * @return The permissions of the given principal. The returned
+     *         <code>int</code> is a bitmask of the permission constants defined
+     *         in this class
+     * @throws IllegalArgumentException if <code>principal</code> is not a
+     *         valid principal name
+     */
+    public synchronized int getPermissions(String principal) {
+        int permissions = 0;
+
+        if (!(ALL_PRINCIPALS.equals(principal))) {
+            checkPrincipal(principal);
+            Object po = principalPermissions.get(principal);
+            if (po != null)
+                permissions = ((Integer) po).intValue();
+        }
+
+        return permissions | globalPermissions;
+    }
+
+    /**
+     * Check whether the given permissions are granted to a certain principal.
+     * The requested permissions are specified as a bitfield, for example
+     * <code>(Acl.ADD | Acl.DELETE | Acl.GET)</code>.
+     * 
+     * @param principal The entity to check, or &quot;*&quot; to check whether
+     *        the given permissions are granted to all principals globally
+     * @param permissions The permissions to check
+     * @return <code>true</code> if the principal holds all the given permissions
+     * @throws IllegalArgumentException if <code>principal</code> is not a
+     *         valid principal name or if <code>permissions</code> is not a
+     *         valid combination of the permission constants defined in this
+     *         class
+     */
+    public synchronized boolean isPermitted(String principal, int permissions) {
+        checkPermissions(permissions);
+
+        int hasPermissions = getPermissions(principal);
+        return (permissions & hasPermissions) == permissions;
+    }
+
+    /**
+     * Create a new <code>Acl</code> instance from this <code>Acl</code> where 
+     * all permissions for the given principal are overwritten with the given 
+     * permissions.
+     * <p>
+     * Note, that when changing the permissions of a specific principal, it is
+     * not allowed to specify a set of permissions stricter than the global set
+     * of permissions (that apply to all principals).
+     * 
+     * @param principal The entity to which permissions should be granted, or
+     *        &quot;*&quot; to globally grant permissions to all principals.
+     * @param permissions The set of permissions to be given. The parameter is
+     *        a bitmask of the permission constants defined in this class.
+     * @return a new <code>Acl</code> instance
+     * @throws IllegalArgumentException if <code>principal</code> is not a
+     *         valid principal name, if <code>permissions</code> is not a
+     *         valid combination of the permission constants defined in this
+     *         class, or if a globally granted permission would have been
+     *         revoked from a specific principal
+     */
+    public synchronized Acl setPermission(String principal, int permissions) {
+        checkPermissions(permissions);
+
+        Acl newPermission = new Acl(this, principal, permissions);
+        return newPermission;
+    }
+
+    /**
+     * Get the list of principals who have any kind of permissions on this node.
+     * The list only includes those principals that have been explicitly
+     * assigned permissions (so &quot;*&quot; is never returned), globally set
+     * permissions naturally apply to all other principals as well.
+     * 
+     * @return The array of principals having permissions on this node.
+     */
+    public String[] getPrincipals() {
+        return (String[]) (principalPermissions.keySet().toArray(new String[0]));
+    }
+
+    /**
+     * Give the canonic string representation of this ACL. The operations are in
+     * the following order: {Add, Delete, Exec, Get, Replace}, principal names
+     * are sorted alphabetically.
+     * 
+     * @return The string representation as defined in OMA DM.
+     */
+    public synchronized String toString() {
+        String acl = null;
+        for (int i = 0; i < PERMISSION_CODES.length; i++)
+            acl = writeEntry(PERMISSION_CODES[i], acl);
+
+        return (acl != null) ? acl : "";
+    }
+
+    // ----- Private utility methods -----//
+
+    private String writeEntry(int command, String acl) {
+        String aclEntry = null;
+
+        if ((command & globalPermissions) > 0)
+            aclEntry = ALL_PRINCIPALS;
+        else {
+            // TreeMap guarantees alphabetical ordering of keys during traversal
+            Iterator i = principalPermissions.entrySet().iterator();
+            while (i.hasNext()) {
+                Map.Entry entry = (Map.Entry) i.next();
+                if ((command & ((Integer) entry.getValue()).intValue()) > 0)
+                    aclEntry = appendEntry(aclEntry, '+', (String) entry
+                            .getKey());
+            }
+        }
+
+        if (aclEntry == null)
+            return acl;
+
+        return appendEntry(acl, '&', writeCommands(command) + '=' + aclEntry);
+    }
+
+    private static void deleteFromAll(TreeMap principalPermissions, int perm) {
+        Iterator i = principalPermissions.entrySet().iterator();
+        while (i.hasNext()) {
+            Map.Entry entry = (Map.Entry) i.next();
+            setPrincipalPermission(principalPermissions, (String) entry
+                    .getKey(), ((Integer) entry.getValue()).intValue() & ~perm);
+        }
+    }
+
+    private static void setPrincipalPermission(TreeMap principalPermissions,
+            String principal, int perm) {
+        if (perm == 0)
+            principalPermissions.remove(principal);
+        else
+            principalPermissions.put(principal, new Integer(perm));
+    }
+
+    private static String writeCommands(int command) {
+        String commandStr = null;
+        for (int i = 0; i < PERMISSION_CODES.length; i++)
+            if ((command & PERMISSION_CODES[i]) != 0)
+                commandStr = appendEntry(commandStr, ',', PERMISSION_NAMES[i]);
+
+        return (commandStr != null) ? commandStr : "";
+    }
+
+    private static String appendEntry(String base, char separator, String entry) {
+        return (base != null) ? base + separator + entry : entry;
+    }
+
+    private static int parseCommand(String command) {
+        int i = Arrays.asList(PERMISSION_NAMES).indexOf(command);
+        if (i == -1)
+            throw new IllegalArgumentException(
+                    "Invalid ACL string: unknown command '" + command + "'.");
+
+        return PERMISSION_CODES[i];
+    }
+
+    private static void checkPermissions(int perm) {
+        if ((perm & ~ALL_PERMISSION) != 0)
+            throw new IllegalArgumentException("Invalid ACL permission value: "
+                    + perm);
+    }
+
+    private static void checkPrincipal(String principal) {
+        if (principal == null)
+            throw new IllegalArgumentException("Principal is null.");
+
+        checkServerId(principal, "Principal name contains illegal character");
+    }
+
+    private static void checkServerId(String serverId, String errorText) {
+        char[] chars = serverId.toCharArray();
+        for (int i = 0; i < chars.length; i++)
+            if ("*=+&".indexOf(chars[i]) != -1
+                    || Character.isWhitespace(chars[i]))
+                throw new IllegalArgumentException(errorText + " '" + chars[i]
+                        + "'.");
+    }
+
+    private static String[] split(String input, char sep, int limit) {
+        Vector v = new Vector();
+        boolean limited = (limit > 0);
+        int applied = 0;
+        int index = 0;
+        StringBuffer part = new StringBuffer();
+
+        while (index < input.length()) {
+            char ch = input.charAt(index);
+            if (ch != sep)
+                part.append(ch);
+            else {
+                ++applied;
+                v.add(part.toString());
+                part = new StringBuffer();
+            }
+            ++index;
+            if (limited && applied == limit - 1)
+                break;
+        }
+        while (index < input.length()) {
+            char ch = input.charAt(index);
+            part.append(ch);
+            ++index;
+        }
+        v.add(part.toString());
+
+        int last = v.size();
+        if (0 == limit) {
+            for (int j = v.size() - 1; j >= 0; --j) {
+                String s = (String) v.elementAt(j);
+                if ("".equals(s))
+                    --last;
+                else
+                    break;
+            }
+        }
+
+        String[] ret = new String[last];
+        for (int i = 0; i < last; ++i)
+            ret[i] = (String) v.elementAt(i);
+
+        return ret;
+    }
+}

Added: felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtAdmin.java
URL: http://svn.apache.org/viewvc/felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtAdmin.java?rev=681945&view=auto
==============================================================================
--- felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtAdmin.java (added)
+++ felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtAdmin.java Sat Aug  2 02:56:01 2008
@@ -0,0 +1,281 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/DmtAdmin.java,v 1.9 2006/07/11 16:59:41 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ * 
+ * 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 info.dmtree;
+
+/**
+ * An interface providing methods to open sessions and register listeners. The
+ * implementation of <code>DmtAdmin</code> should register itself in the OSGi
+ * service registry as a service. <code>DmtAdmin</code> is the entry point for
+ * applications to use the DMT API.
+ * <p>
+ * The <code>getSession</code> methods are used to open a session on a
+ * specified subtree of the DMT. A typical way of usage:
+ * <pre>
+ * serviceRef = context.getServiceReference(DmtAdmin.class.getName());
+ * DmtAdmin admin = (DmtAdmin) context.getService(serviceRef);
+ * DmtSession session = admin.getSession(&quot;./OSGi/Configuration&quot;);
+ * session.createInteriorNode(&quot;./OSGi/Configuration/my.table&quot;);
+ * </pre>
+ * <p>
+ * The methods for opening a session take a node URI (the session root) as a
+ * parameter. All segments of the given URI must be within the segment length
+ * limit of the implementation, and the special characters '/' and '\' must be
+ * escaped (preceded by a '\'). Any string can be converted to a valid URI
+ * segment using the {@link Uri#mangle(String)} method.
+ * <p>
+ * It is possible to specify a lock mode when opening the session (see lock type
+ * constants in {@link DmtSession}).  This determines whether the session can
+ * run in parallel with other sessions, and the kinds of operations that can be 
+ * performed in the session.  All Management Objects constituting the device 
+ * management tree must support read operations on their nodes, while support 
+ * for write operations depends on the Management Object. Management Objects 
+ * supporting write access may support transactional write, non-transactional 
+ * write or both. Users of <code>DmtAdmin</code> should consult the Management 
+ * Object specification and implementation for the supported update modes. If 
+ * Management Object definition permits, implementations are encouraged to 
+ * support both update modes. 
+ * <p>
+ * This interface also contains methods for manipulating the set of
+ * <code>DmtEventListener</code> objects that are called when the structure or
+ * content of the tree is changed. These methods are not needed in an OSGi
+ * environment, clients should register listeners through the Event Admin 
+ * service.
+ */
+public interface DmtAdmin {
+    /**
+     * Opens a <code>DmtSession</code> for local usage on a given subtree of
+     * the DMT with non transactional write lock. This call is equivalent to the
+     * following:
+     * <code>getSession(null, subtreeUri, DmtSession.LOCK_TYPE_EXCLUSIVE)</code>
+     * <p>
+     * The <code>subtreeUri</code> parameter must contain an absolute URI.  It
+     * can also be <code>null</code>, in this case the session is opened with 
+     * the default session root, &quot;.&quot;, that gives access to the whole 
+     * tree.
+     * <p>
+     * To perform this operation the caller must have <code>DmtPermission</code>
+     * for the <code>subtreeUri</code> node with the Get action present.
+     * 
+     * @param subtreeUri the subtree on which DMT manipulations can be performed
+     *        within the returned session
+     * @return a <code>DmtSession</code> object for the requested subtree
+     * @throws DmtException with the following possible error codes:
+     *         <ul>
+     *         <li><code>URI_TOO_LONG</code> if <code>subtreeUri</code> or
+     *         a segment of it is too long, or if it has too many segments
+     *         <li><code>INVALID_URI</code> if <code>subtreeUri</code> is
+     *         syntactically invalid
+     *         <li><code>NODE_NOT_FOUND</code> if <code>subtreeUri</code>
+     *         specifies a non-existing node
+     *         <li><code>SESSION_CREATION_TIMEOUT</code> if the operation
+     *         timed out because of another ongoing session
+     *         <li><code>COMMAND_FAILED</code> if <code>subtreeUri</code>
+     *         specifies a relative URI, or some unspecified error is
+     *         encountered while attempting to complete the command
+     *         </ul>
+     * @throws SecurityException if the caller does not have 
+     *         <code>DmtPermission</code> for the given root node with the Get 
+     *         action present 
+     */
+    DmtSession getSession(String subtreeUri) throws DmtException;
+
+    /**
+     * Opens a <code>DmtSession</code> for local usage on a specific DMT
+     * subtree with a given lock mode. This call is equivalent to the
+     * following: <code>getSession(null, subtreeUri, lockMode)</code>
+     * <p>
+     * The <code>subtreeUri</code> parameter must contain an absolute URI.  It
+     * can also be <code>null</code>, in this case the session is opened with 
+     * the default session root, &quot;.&quot;, that gives access to the whole 
+     * tree.
+     * <p>
+     * To perform this operation the caller must have <code>DmtPermission</code>
+     * for the <code>subtreeUri</code> node with the Get action present.
+     * 
+     * @param subtreeUri the subtree on which DMT manipulations can be performed
+     *        within the returned session
+     * @param lockMode one of the lock modes specified in
+     *        <code>DmtSession</code>
+     * @return a <code>DmtSession</code> object for the requested subtree
+     * @throws DmtException with the following possible error codes:
+     *         <ul>
+     *         <li><code>URI_TOO_LONG</code> if <code>subtreeUri</code> or
+     *         a segment of it is too long, or if it has too many segments
+     *         <li><code>INVALID_URI</code> if <code>subtreeUri</code> is
+     *         syntactically invalid
+     *         <li><code>NODE_NOT_FOUND</code> if <code>subtreeUri</code>
+     *         specifies a non-existing node
+     *         <li><code>FEATURE_NOT_SUPPORTED</code> if atomic sessions are
+     *         not supported by the implementation and <code>lockMode</code> 
+     *         requests an atomic session
+     *         <li><code>SESSION_CREATION_TIMEOUT</code> if the operation 
+     *         timed out because of  another ongoing session
+     *         <li><code>COMMAND_FAILED</code> if <code>subtreeUri</code>
+     *         specifies a relative URI, if <code>lockMode</code> is unknown,
+     *         or some unspecified error is encountered while attempting to 
+     *         complete the command
+     *         </ul>
+     * @throws SecurityException if the caller does not have 
+     *         <code>DmtPermission</code> for the given root node with the Get 
+     *         action present 
+     */
+    DmtSession getSession(String subtreeUri, int lockMode) throws DmtException;
+
+    /**
+     * Opens a <code>DmtSession</code> on a specific DMT subtree using a
+     * specific lock mode on behalf of a remote principal. If local management
+     * applications are using this method then they should provide
+     * <code>null</code> as the first parameter. Alternatively they can use
+     * other forms of this method without providing a principal string. 
+     * <p>
+     * The <code>subtreeUri</code> parameter must contain an absolute URI.  It
+     * can also be <code>null</code>, in this case the session is opened with 
+     * the default session root, &quot;.&quot;, that gives access to the whole 
+     * tree.  
+     * <p>
+     * This method is guarded by <code>DmtPrincipalPermission</code> in case of
+     * remote sessions.  In addition, the caller must have Get access rights 
+     * (ACL in case of remote sessions, <code>DmtPermission</code> in case of
+     * local sessions) on the <code>subtreeUri</code> node to perform this
+     * operation. 
+     * 
+     * @param principal the identifier of the remote server on whose behalf the
+     *        data manipulation is performed, or <code>null</code> for local
+     *        sessions
+     * @param subtreeUri the subtree on which DMT manipulations can be performed
+     *        within the returned session
+     * @param lockMode one of the lock modes specified in
+     *        <code>DmtSession</code>
+     * @return a <code>DmtSession</code> object for the requested subtree
+     * @throws DmtException with the following possible error codes:
+     *         <ul>
+     *         <li><code>URI_TOO_LONG</code> if <code>subtreeUri</code> or
+     *         a segment of it is too long, or if it has too many segments
+     *         <li><code>INVALID_URI</code> if <code>subtreeUri</code> is
+     *         syntactically invalid
+     *         <li><code>NODE_NOT_FOUND</code> if <code>subtreeUri</code>
+     *         specifies a non-existing node
+     *         <li><code>PERMISSION_DENIED</code> if <code>principal</code> is
+     *         not <code>null</code> and the ACL of the node does not allow the
+     *         <code>Get</code> operation for the principal on the given root 
+     *         node 
+     *         <li><code>FEATURE_NOT_SUPPORTED</code> if atomic sessions are
+     *         not supported by the implementation and <code>lockMode</code> 
+     *         requests an atomic session
+     *         <li><code>SESSION_CREATION_TIMEOUT</code> if the operation
+     *         timed out because of  another ongoing session
+     *         <li><code>COMMAND_FAILED</code> if <code>subtreeUri</code>
+     *         specifies a relative URI, if <code>lockMode</code> is unknown,
+     *         or some unspecified error is encountered while attempting to 
+     *         complete the command
+     *         </ul>
+     * @throws SecurityException in case of remote sessions, if the caller does 
+     *         not have the required <code>DmtPrincipalPermission</code> with a 
+     *         target matching the <code>principal</code> parameter, or in case
+     *         of local sessions, if the caller does not have 
+     *         <code>DmtPermission</code> for the given root node with the Get 
+     *         action present 
+     */
+    DmtSession getSession(String principal, String subtreeUri, int lockMode)
+            throws DmtException;
+
+    /**
+     * Registers an event listener on behalf of a local application. The given
+     * listener will receive notification on all changes affecting the specified
+     * subtree. The subtree is specified by its root node URI. An event is
+     * delivered to the registered listener if at least one affected node is
+     * within this subtree. The events can also be filtered by specifying a
+     * bitmask of relevant event types (e.g.
+     * <code>DmtEvent.ADDED | DmtEvent.REPLACED | DmtEvent.SESSION_CLOSED</code>).
+     * Only event types included in the bitmask will be delivered to the
+     * listener.
+     * <p>
+     * The listener will only receive the change notifications of nodes for
+     * which the registering application has the appropriate GET
+     * {@link info.dmtree.security.DmtPermission}.
+     * <p>
+     * If the specified <code>listener</code> was already registered, calling
+     * this method will update the registration.
+     * 
+     * @param type a bitmask of event types the caller is interested in
+     * @param uri the URI of the root node of a subtree, must not be
+     *        <code>null</code>
+     * @param listener the listener to be registered, must not be
+     *        <code>null</code>
+     * @throws SecurityException if the caller doesn't have the necessary GET
+     *         <code>DmtPermission</code> for the given URI
+     * @throws NullPointerException if the <code>uri</code> or
+     *         <code>listener</code> parameter is <code>null</code>
+     * @throws IllegalArgumentException if the <code>type</code> parameter
+     *         contains invalid bits (not corresponding to any event type
+     *         defined in <code>DmtEvent</code>), or if the <code>uri</code>
+     *         parameter is invalid (is not an absolute URI or is syntactically
+     *         incorrect)
+     */
+    void addEventListener(int type, String uri, DmtEventListener listener);
+
+    /**
+     * Registers an event listener on behalf of a remote principal. The given
+     * listener will receive notification on all changes affecting the specified
+     * subtree. The subtree is specified by its root node URI. An event is
+     * delivered to the registered listener if at least one affected node is
+     * within this subtree. The events can also be filtered by specifying a
+     * bitmask of relevant event types (e.g.
+     * <code>DmtEvent.ADDED | DmtEvent.REPLACED | DmtEvent.SESSION_CLOSED</code>).
+     * Only event types included in the bitmask will be delivered to the
+     * listener.
+     * <p>
+     * The listener will only receive the change notifications of nodes for
+     * which the node ACL grants GET access to the specified principal.
+     * <p>
+     * If the specified <code>listener</code> was already registered, calling
+     * this method will update the registration.
+     * 
+     * @param principal the management server identity the caller is acting on
+     *        behalf of, must not be <code>null</code>
+     * @param type a bitmask of event types the caller is interested in
+     * @param uri the URI of the root node of a subtree, must not be
+     *        <code>null</code>
+     * @param listener the listener to be registered, must not be
+     *        <code>null</code>
+     * @throws SecurityException if the caller doesn't have the necessary
+     *         <code>DmtPrincipalPermission</code> to use the specified
+     *         principal
+     * @throws NullPointerException if the <code>principal</code>,
+     *         <code>uri</code> or <code>listener</code> parameter is 
+     *         <code>null</code>
+     * @throws IllegalArgumentException if the <code>type</code> parameter
+     *         contains invalid bits (not corresponding to any event type
+     *         defined in <code>DmtEvent</code>), or if the <code>uri</code>
+     *         parameter is invalid (is not an absolute URI or is syntactically
+     *         incorrect)
+     */
+    void addEventListener(String principal, int type, String uri,
+            DmtEventListener listener);
+
+    /**
+     * Remove a previously registered listener. After this call, the listener
+     * will not receive change notifications.
+     * 
+     * @param listener the listener to be unregistered, must not be
+     *        <code>null</code>
+     * @throws NullPointerException if the <code>listener</code> parameter is
+     *         <code>null</code>
+     */
+    void removeEventListener(DmtEventListener listener);
+}

Added: felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java
URL: http://svn.apache.org/viewvc/felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java?rev=681945&view=auto
==============================================================================
--- felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java (added)
+++ felix/trunk/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java Sat Aug  2 02:56:01 2008
@@ -0,0 +1,907 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/DmtData.java,v 1.8 2006/07/10 21:37:07 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ * 
+ * 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 info.dmtree;
+
+import java.util.Arrays;
+import java.util.Hashtable;
+
+// Possible enhancements to this class:
+// * new constructors and get/set methods for b64, to access the encoded value
+// * new constructors and get/set methods for date/time, for more convenient 
+//   Java access
+/**
+ * An immutable data structure representing the contents of a leaf or interior
+ * node. This structure represents only the value and the format property of the
+ * node, all other properties (like MIME type) can be set and read using the
+ * <code>DmtSession</code> interface.
+ * <p>
+ * Different constructors are available to create nodes with different formats.
+ * Nodes of <code>null</code> format can be created using the static
+ * {@link #NULL_VALUE} constant instance of this class.
+ * <p>
+ * {@link #FORMAT_RAW_BINARY} and {@link #FORMAT_RAW_STRING} enable the support
+ * of future data formats. When using these formats, the actual format name is
+ * specified as a <code>String</code>. The application is responsible for the
+ * proper encoding of the data according to the specified format.
+ */
+public final class DmtData {
+
+    /**
+     * The node holds an OMA DM <code>int</code> value.
+     */
+    public static final int FORMAT_INTEGER = 0x0001;
+
+    /**
+     * The node holds an OMA DM <code>float</code> value.
+     */
+    public static final int FORMAT_FLOAT = 0x0002;
+
+    /**
+     * The node holds an OMA DM <code>chr</code> value.
+     */
+    public static final int FORMAT_STRING = 0x0004;
+
+    /**
+     * The node holds an OMA DM <code>bool</code> value.
+     */
+    public static final int FORMAT_BOOLEAN = 0x0008;
+
+    /**
+     * The node holds an OMA DM <code>date</code> value.
+     */
+    public static final int FORMAT_DATE = 0x0010;
+
+    /**
+     * The node holds an OMA DM <code>time</code> value.
+     */
+    public static final int FORMAT_TIME = 0x0020;
+
+    /**
+     * The node holds an OMA DM <code>bin</code> value. The value of the node
+     * corresponds to the Java <code>byte[]</code> type.
+     */
+    public static final int FORMAT_BINARY = 0x0040;
+
+    /**
+     * The node holds an OMA DM <code>b64</code> value. Like
+     * {@link #FORMAT_BINARY}, this format is also represented by the Java
+     * <code>byte[]</code> type, the difference is only in the corresponding
+     * OMA DM format.
+     */
+    public static final int FORMAT_BASE64 = 0x0080;
+
+    /**
+     * The node holds an OMA DM <code>xml</code> value.
+     */
+    public static final int FORMAT_XML = 0x0100;
+
+    /**
+     * The node holds an OMA DM <code>null</code> value. This corresponds to
+     * the Java <code>null</code> type.
+     */
+    public static final int FORMAT_NULL = 0x0200;
+
+    /**
+     * Format specifier of an internal node. An interior node can hold a Java
+     * object as value (see {@link DmtData#DmtData(Object)} and
+     * {@link DmtData#getNode()}). This value can be used by Java programs that
+     * know a specific URI understands the associated Java type. This type is
+     * further used as a return value of the {@link MetaNode#getFormat} method
+     * for interior nodes.
+     */
+    public static final int FORMAT_NODE = 0x0400;
+    
+    /**
+     * The node holds raw protocol data encoded as <code>String</code>. The
+     * {@link #getFormatName()} method can be used to get the actual format
+     * name.
+     */
+    public static final int FORMAT_RAW_STRING = 0x0800;
+
+    /**
+     * The node holds raw protocol data encoded in binary format. The
+     * {@link #getFormatName()} method can be used to get the actual format
+     * name.
+     */
+    public static final int FORMAT_RAW_BINARY = 0x1000;
+
+    
+    private static final Hashtable FORMAT_NAMES = new Hashtable();
+    
+    static {
+        FORMAT_NAMES.put(new Integer(FORMAT_BASE64),    "b64");
+        FORMAT_NAMES.put(new Integer(FORMAT_BINARY),    "bin");
+        FORMAT_NAMES.put(new Integer(FORMAT_BOOLEAN),   "bool");
+        FORMAT_NAMES.put(new Integer(FORMAT_DATE),      "date");
+        FORMAT_NAMES.put(new Integer(FORMAT_FLOAT),     "float");
+        FORMAT_NAMES.put(new Integer(FORMAT_INTEGER),   "int");
+        FORMAT_NAMES.put(new Integer(FORMAT_NODE),      "node");
+        FORMAT_NAMES.put(new Integer(FORMAT_NULL),      "null");
+        FORMAT_NAMES.put(new Integer(FORMAT_STRING),    "chr");
+        FORMAT_NAMES.put(new Integer(FORMAT_TIME),      "time");
+        FORMAT_NAMES.put(new Integer(FORMAT_XML),       "xml");
+    }
+
+    /**
+     * Constant instance representing a leaf node of <code>null</code> format.
+     */
+    public static final DmtData NULL_VALUE = new DmtData();
+        // FORMAT_NAMES must be initialized by the time the constr. is called 
+    
+    private final String str;
+
+    private final int integer;
+
+    private final float flt;
+
+    private final boolean bool;
+
+    private final byte[] bytes;
+
+    private final int format;
+    
+    private final String formatName;
+
+    private final Object complex;
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>null</code> format.
+     * This constructor is private and used only to create the public
+     * {@link #NULL_VALUE} constant.
+     */
+    private DmtData() {
+        format = FORMAT_NULL;
+        formatName = getFormatName(format);
+
+        this.str = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>chr</code> format
+     * with the given string value. The <code>null</code> string argument is
+     * valid.
+     * 
+     * @param str the string value to set
+     */
+    public DmtData(String str) {
+        format = FORMAT_STRING;
+        formatName = getFormatName(format);
+        this.str = str;
+
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>node</code> format
+     * with the given object value. The value represents complex data associated
+     * with an interior node.
+     * <p>
+     * Certain interior nodes can support access to their subtrees through such
+     * complex values, making it simpler to retrieve or update all leaf nodes in
+     * a subtree.
+     * <p>
+     * The given value must be a non-<code>null</code> immutable object.
+     * 
+     * @param complex the complex data object to set
+     */
+    public DmtData(Object complex) {
+        if(complex == null)
+            throw new NullPointerException("Complex data argument is null.");
+
+        format = FORMAT_NODE;
+        formatName = getFormatName(format);
+        this.complex = complex;
+
+        this.str     = null;
+        this.integer = 0;
+        this.flt     = 0;
+        this.bool    = false;
+        this.bytes   = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of the specified format and set
+     * its value based on the given string. Only the following string-based
+     * formats can be created using this constructor:
+     * <ul>
+     * <li>{@link #FORMAT_STRING} - value can be any string
+     * <li>{@link #FORMAT_XML} - value must contain an XML fragment (the
+     *     validity is not checked by this constructor)
+     * <li>{@link #FORMAT_DATE} - value must be parseable to an ISO 8601
+     *     calendar date in complete representation, basic format (pattern
+     * <tt>CCYYMMDD</tt>)
+     * <li>{@link #FORMAT_TIME} - value must be parseable to an ISO 8601 time
+     *     of day in either local time, complete representation, basic format
+     *     (pattern <tt>hhmmss</tt>) or Coordinated Universal Time, basic format
+     *     (pattern <tt>hhmmssZ</tt>)
+     * </ul>
+     * The <code>null</code> string argument is only valid if the format is
+     * string or XML.
+     * 
+     * @param value the string, XML, date or time value to set
+     * @param format the format of the <code>DmtData</code> instance to be
+     *        created, must be one of the formats specified above
+     * @throws IllegalArgumentException if <code>format</code> is not one of
+     *         the allowed formats, or <code>value</code> is not a valid
+     *         string for the given format
+     * @throws NullPointerException if a date or time is constructed and
+     *         <code>value</code> is <code>null</code>
+     */
+    public DmtData(String value, int format) {
+        switch (format) {
+        case FORMAT_DATE:
+            checkDateFormat(value);
+            break;
+        case FORMAT_TIME:
+            checkTimeFormat(value);
+            break;
+        case FORMAT_STRING:
+        case FORMAT_XML:
+            break; // nothing to do, all string values are accepted
+        default:
+            throw new IllegalArgumentException(
+                    "Invalid format in string constructor: " + format);
+        }
+        this.format = format;
+        this.formatName = getFormatName(format);
+        this.str = value;
+
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>int</code> format and
+     * set its value.
+     * 
+     * @param integer the integer value to set
+     */
+    public DmtData(int integer) {
+        format = FORMAT_INTEGER;
+        formatName = getFormatName(format);
+        this.integer = integer;
+
+        this.str = null;
+        this.flt = 0;
+        this.bool = false;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>float</code> format
+     * and set its value.
+     * 
+     * @param flt the float value to set
+     */
+    public DmtData(float flt) {
+        format = FORMAT_FLOAT;
+        formatName = getFormatName(format);
+        this.flt = flt;
+
+        this.str = null;
+        this.integer = 0;
+        this.bool = false;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>bool</code> format
+     * and set its value.
+     * 
+     * @param bool the boolean value to set
+     */
+    public DmtData(boolean bool) {
+        format = FORMAT_BOOLEAN;
+        formatName = getFormatName(format);
+        this.bool = bool;
+
+        this.str = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>bin</code> format and
+     * set its value.
+     * 
+     * @param bytes the byte array to set, must not be <code>null</code>
+     * @throws NullPointerException if <code>bytes</code> is <code>null</code>
+     */
+    public DmtData(byte[] bytes) {
+        if (bytes == null)
+            throw new NullPointerException("Binary data argument is null.");
+
+        format = FORMAT_BINARY;
+        formatName = getFormatName(format);
+        this.bytes = bytes;
+
+        this.str = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>bin</code> or
+     * <code>b64</code> format and set its value. The chosen format is
+     * specified by the <code>base64</code> parameter.
+     * 
+     * @param bytes the byte array to set, must not be <code>null</code>
+     * @param base64 if <code>true</code>, the new instance will have
+     *        <code>b64</code> format, if <code>false</code>, it will have
+     *        <code>bin</code> format
+     * @throws NullPointerException if <code>bytes</code> is <code>null</code>
+     */
+    public DmtData(byte[] bytes, boolean base64) {
+        if (bytes == null)
+            throw new NullPointerException("Binary data argument is null.");
+
+        format = base64 ? FORMAT_BASE64 : FORMAT_BINARY;
+        formatName = getFormatName(format);
+        this.bytes = bytes;
+
+        this.str = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance in {@link #FORMAT_RAW_STRING}
+     * format. The data is provided encoded as a <code>String</code>. The
+     * actual data format is specified in <code>formatName</code>. The
+     * encoding used in <code>data</code> must conform to this format.
+     * 
+     * @param formatName the name of the format, must not be <code>null</code>
+     * @param data the data encoded according to the specified format, must not
+     *        be <code>null</code>
+     * @throws NullPointerException if <code>formatName</code> or
+     *         <code>data</code> is <code>null</code>
+     */
+    public DmtData(String formatName, String data) {
+        if(formatName == null)
+            throw new NullPointerException("Format name argument is null.");
+        if(data == null)
+            throw new NullPointerException("Data argument is null.");
+        
+        format = FORMAT_RAW_STRING;
+        this.formatName = formatName;
+        this.str = data;
+
+        this.bytes = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.complex = null;
+    }
+    
+    /**
+     * Create a <code>DmtData</code> instance in {@link #FORMAT_RAW_BINARY}
+     * format. The data is provided encoded as binary. The actual data format is
+     * specified in <code>formatName</code>. The encoding used in
+     * <code>data</code> must conform to this format.
+     * 
+     * @param formatName the name of the format, must not be <code>null</code>
+     * @param data the data encoded according to the specified format, must not
+     *        be <code>null</code>
+     * @throws NullPointerException if <code>formatName</code> or
+     *         <code>data</code> is <code>null</code>
+     */
+    public DmtData(String formatName, byte[] data) {
+        if(formatName == null)
+            throw new NullPointerException("Format name argument is null.");
+        if(data == null)
+            throw new NullPointerException("Data argument is null.");
+
+        format = FORMAT_RAW_BINARY;
+        this.formatName = formatName;
+        this.bytes = (byte[]) data.clone();
+
+        this.str = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.complex = null;
+    }
+    
+    /**
+     * Gets the value of a node with string (<code>chr</code>) format.
+     * 
+     * @return the string value
+     * @throws DmtIllegalStateException if the format of the node is not string
+     */
+    public String getString() {
+        if (format == FORMAT_STRING)
+            return str;
+
+        throw new DmtIllegalStateException("DmtData value is not string.");
+    }
+
+    /**
+     * Gets the value of a node with date format. The returned date string is
+     * formatted according to the ISO 8601 definition of a calendar date in
+     * complete representation, basic format (pattern <tt>CCYYMMDD</tt>).
+     * 
+     * @return the date value
+     * @throws DmtIllegalStateException if the format of the node is not date
+     */
+    public String getDate() {
+        if (format == FORMAT_DATE)
+            return str;
+
+        throw new DmtIllegalStateException("DmtData value is not date.");
+    }
+
+    /**
+     * Gets the value of a node with time format. The returned time string is
+     * formatted according to the ISO 8601 definition of the time of day. The
+     * exact format depends on the value the object was initialized with: either
+     * local time, complete representation, basic format (pattern
+     * <tt>hhmmss</tt>) or Coordinated Universal Time, basic format (pattern
+     * <tt>hhmmssZ</tt>).
+     * 
+     * @return the time value
+     * @throws DmtIllegalStateException if the format of the node is not time
+     */
+    public String getTime() {
+        if (format == FORMAT_TIME)
+            return str;
+
+        throw new DmtIllegalStateException("DmtData value is not time.");
+    }
+
+    /**
+     * Gets the value of a node with <code>xml</code> format.
+     * 
+     * @return the XML value
+     * @throws DmtIllegalStateException if the format of the node is not
+     *         <code>xml</code>
+     */
+    public String getXml() {
+        if (format == FORMAT_XML)
+            return str;
+
+        throw new DmtIllegalStateException("DmtData value is not XML.");
+    }
+
+    /**
+     * Gets the value of a node with integer (<code>int</code>) format.
+     * 
+     * @return the integer value
+     * @throws DmtIllegalStateException if the format of the node is not integer
+     */
+    public int getInt() {
+        if (format == FORMAT_INTEGER)
+            return integer;
+
+        throw new DmtIllegalStateException("DmtData value is not integer.");
+    }
+
+    /**
+     * Gets the value of a node with <code>float</code> format.
+     * 
+     * @return the float value
+     * @throws DmtIllegalStateException if the format of the node is not
+     *         <code>float</code>
+     */
+    public float getFloat() {
+        if (format == FORMAT_FLOAT)
+            return flt;
+
+        throw new DmtIllegalStateException("DmtData value is not float.");
+    }
+
+    /**
+     * Gets the value of a node with boolean (<code>bool</code>) format.
+     * 
+     * @return the boolean value
+     * @throws DmtIllegalStateException if the format of the node is not boolean
+     */
+    public boolean getBoolean() {
+        if (format == FORMAT_BOOLEAN)
+            return bool;
+
+        throw new DmtIllegalStateException("DmtData value is not boolean.");
+    }
+
+    /**
+     * Gets the value of a node with binary (<code>bin</code>) format.
+     * 
+     * @return the binary value
+     * @throws DmtIllegalStateException if the format of the node is not binary
+     */
+    public byte[] getBinary() {
+        if (format == FORMAT_BINARY) {
+            byte[] bytesCopy = new byte[bytes.length];
+            for (int i = 0; i < bytes.length; i++)
+                bytesCopy[i] = bytes[i];
+
+            return bytesCopy;
+        }
+
+        throw new DmtIllegalStateException("DmtData value is not a byte array.");
+    }
+    
+    /**
+     * Gets the value of a node in raw binary ({@link #FORMAT_RAW_BINARY})
+     * format.
+     * 
+     * @return the data value in raw binary format
+     * @throws DmtIllegalStateException if the format of the node is not raw binary
+     */
+    public byte[] getRawBinary() {
+        if (format == FORMAT_RAW_BINARY)
+            return (byte[]) bytes.clone();
+        
+        throw new DmtIllegalStateException(
+                "DmtData value is not in raw binary format.");
+    }
+
+    /**
+     * Gets the value of a node in raw <code>String</code>
+     * ({@link #FORMAT_RAW_STRING}) format.
+     * 
+     * @return the data value in raw <code>String</code> format
+     * @throws DmtIllegalStateException if the format of the node is not raw
+     *     <code>String</code>
+    */
+    public String getRawString() {
+        if (format == FORMAT_RAW_STRING)
+            return str;
+        
+        throw new DmtIllegalStateException(
+                "DmtData value is not in raw string format.");
+    }
+
+    /**
+     * Gets the value of a node with base 64 (<code>b64</code>) format.
+     * 
+     * @return the binary value
+     * @throws DmtIllegalStateException if the format of the node is not base 64.
+     */
+    public byte[] getBase64() {
+        if (format == FORMAT_BASE64) {
+            byte[] bytesCopy = new byte[bytes.length];
+            for (int i = 0; i < bytes.length; i++)
+                bytesCopy[i] = bytes[i];
+
+            return bytesCopy;
+        }
+
+        throw new DmtIllegalStateException(
+                "DmtData value is not in base 64 format.");
+    }
+
+    /**
+     * Gets the complex data associated with an interior node (<code>node</code>
+     * format).
+     * <p>
+     * Certain interior nodes can support access to their subtrees through
+     * complex values, making it simpler to retrieve or update all leaf nodes in
+     * the subtree.
+     * 
+     * @return the data object associated with an interior node
+     * @throws DmtIllegalStateException if the format of the data is not 
+     *         <code>node</code>
+     */
+    public Object getNode() {
+        if(format == FORMAT_NODE)
+            return complex;
+
+        throw new DmtIllegalStateException(
+                "DmtData does not contain interior node data.");
+    }
+
+    /**
+     * Get the node's format, expressed in terms of type constants defined in
+     * this class. Note that the 'format' term is a legacy from OMA DM, it is
+     * more customary to think of this as 'type'.
+     * 
+     * @return the format of the node
+     */
+    public int getFormat() {
+        return format;
+    }
+
+    /**
+     * Returns the format of this <code>DmtData</code> as <code>String</code>.
+     * For the predefined data formats this is the OMA DM defined name of the
+     * format. For {@link #FORMAT_RAW_STRING} and {@link #FORMAT_RAW_BINARY}
+     * this is the format specified when the object was created.
+     * 
+     * @return the format name as <code>String</code>
+     */
+    public String getFormatName() {
+        return formatName;
+    }
+
+    /**
+     * Get the size of the data. The returned value depends on the format of
+     * data in the node:
+     * <ul>
+     * <li>{@link #FORMAT_STRING}, {@link #FORMAT_XML}, {@link #FORMAT_BINARY},
+     *     {@link #FORMAT_BASE64}, {@link #FORMAT_RAW_STRING}, and
+     *     {@link #FORMAT_RAW_BINARY}: the length of the stored data, or 0 if
+     *     the data is <code>null</code>
+     * <li>{@link #FORMAT_INTEGER} and {@link #FORMAT_FLOAT}: 4
+     * <li>{@link #FORMAT_DATE} and {@link #FORMAT_TIME}: the length of the
+     *     date or time in its string representation
+     * <li>{@link #FORMAT_BOOLEAN}: 1
+     * <li>{@link #FORMAT_NODE}: -1 (unknown)
+     * <li>{@link #FORMAT_NULL}: 0
+     * </ul>
+     * 
+     * @return the size of the data stored by this object
+     */
+    public int getSize() {
+        switch (format) {
+        case FORMAT_STRING:
+        case FORMAT_XML:
+        case FORMAT_DATE:
+        case FORMAT_TIME:
+        case FORMAT_RAW_STRING:
+            return str == null ? 0 : str.length();
+        case FORMAT_BINARY:
+        case FORMAT_BASE64:
+        case FORMAT_RAW_BINARY:
+            return bytes.length;
+        case FORMAT_INTEGER:
+        case FORMAT_FLOAT:
+            return 4;
+        case FORMAT_BOOLEAN:
+            return 1;
+        case FORMAT_NODE:
+            return -1;
+        case FORMAT_NULL:
+            return 0;
+        }
+
+        return 0; // never reached
+    }
+
+    /**
+     * Gets the string representation of the <code>DmtData</code>. This
+     * method works for all formats.
+     * <p>
+     * For string format data - including {@link #FORMAT_RAW_STRING} - the
+     * string value itself is returned, while for XML, date, time, integer,
+     * float, boolean and node formats the string form of the value is returned.
+     * Binary - including {@link #FORMAT_RAW_BINARY} - and base64 data is
+     * represented by two-digit hexadecimal numbers for each byte separated by
+     * spaces. The {@link #NULL_VALUE} data has the string form of
+     * "<code>null</code>". Data of string or XML format containing the Java
+     * <code>null</code> value is represented by an empty string.
+     * 
+     * @return the string representation of this <code>DmtData</code> instance
+     */
+    public String toString() {
+        switch (format) {
+        case FORMAT_STRING:
+        case FORMAT_XML:
+        case FORMAT_DATE:
+        case FORMAT_TIME:
+        case FORMAT_RAW_STRING:
+            return str == null ? "" : str;
+        case FORMAT_INTEGER:
+            return String.valueOf(integer);
+        case FORMAT_FLOAT:
+            return String.valueOf(flt);
+        case FORMAT_BOOLEAN:
+            return String.valueOf(bool);
+        case FORMAT_BINARY:
+        case FORMAT_BASE64:
+        case FORMAT_RAW_BINARY:
+            return getHexDump(bytes);
+        case FORMAT_NODE:
+            return complex.toString();
+        case FORMAT_NULL:
+            return "null";
+        }
+
+        return null; // never reached
+    }
+
+    /**
+     * Compares the specified object with this <code>DmtData</code> instance.
+     * Two <code>DmtData</code> objects are considered equal if their format
+     * is the same, and their data (selected by the format) is equal.
+     * <p>
+     * In case of {@link #FORMAT_RAW_BINARY} and {@link #FORMAT_RAW_STRING}
+     * the textual name of the data format - as returned by
+     * {@link #getFormatName()} - must be equal as well.
+     * 
+     * @param obj the object to compare with this <code>DmtData</code>
+     * @return true if the argument represents the same <code>DmtData</code>
+     *         as this object
+     */
+    public boolean equals(Object obj) {
+        if (!(obj instanceof DmtData))
+            return false;
+
+        DmtData other = (DmtData) obj;
+
+        if (format != other.format)
+            return false;
+
+        switch (format) {
+        case FORMAT_STRING:
+        case FORMAT_XML:
+        case FORMAT_DATE:
+        case FORMAT_TIME:
+            return str == null ? other.str == null : str.equals(other.str);
+        case FORMAT_INTEGER:
+            return integer == other.integer;
+        case FORMAT_FLOAT:
+            return flt == other.flt;
+        case FORMAT_BOOLEAN:
+            return bool == other.bool;
+        case FORMAT_BINARY:
+        case FORMAT_BASE64:
+            return Arrays.equals(bytes, other.bytes);
+        case FORMAT_NODE:
+            return complex.equals(other.complex);
+        case FORMAT_NULL:
+            return true;
+        case FORMAT_RAW_BINARY:
+            return formatName.equals(other.formatName)
+                    && Arrays.equals(bytes, other.bytes);
+        case FORMAT_RAW_STRING:
+            // in this case str cannot be null
+            return formatName.equals(other.formatName) && str.equals(other.str);
+        }
+
+        return false; // never reached
+    }
+
+    /**
+     * Returns the hash code value for this <code>DmtData</code> instance. The
+     * hash code is calculated based on the data (selected by the format) of
+     * this object.
+     * 
+     * @return the hash code value for this object
+     */
+    public int hashCode() {
+        switch (format) {
+        case FORMAT_STRING:
+        case FORMAT_XML:
+        case FORMAT_DATE:
+        case FORMAT_TIME:
+        case FORMAT_RAW_STRING:
+            return str == null ? 0 : str.hashCode();
+        case FORMAT_INTEGER:
+            return new Integer(integer).hashCode();
+        case FORMAT_FLOAT:
+            return new Float(flt).hashCode();
+        case FORMAT_BOOLEAN:
+            return new Boolean(bool).hashCode();
+        case FORMAT_BINARY:
+        case FORMAT_BASE64:
+        case FORMAT_RAW_BINARY:
+            return new String(bytes).hashCode();
+        case FORMAT_NODE:
+            return complex.hashCode();
+        case FORMAT_NULL:
+            return 0;
+        }
+
+        return 0; // never reached
+    }
+    
+    private static void checkDateFormat(String value) {
+        if(value.length() != 8)
+            throw new IllegalArgumentException("Date string '" + value +
+                    "' does not follow the format 'CCYYMMDD'.");
+        
+        int year = checkNumber(value, "Date", 0, 4, 0, 9999);
+        int month = checkNumber(value, "Date", 4, 2, 1, 12);
+        int day = checkNumber(value, "Date", 6, 2, 1, 31);
+        
+        // Date checking is not prepared for all special rules (for example
+        // historical leap years), production code could contain a full check.
+        
+        // Day 31 is invalid for April, June, September and November
+        if((month == 4 || month == 6 || month == 9 || month == 11) && day == 31)
+        	throw new IllegalArgumentException("Date string '" + value +
+        			"' contains an invalid date.");
+        
+        // February 29 is invalid except for leap years, Feb. 30-31 are invalid
+        if(month == 2 && day > 28 &&
+        	!(day == 29 && year%4 == 0 && (year%100 != 0 || year%400 == 0)))
+        	throw new IllegalArgumentException("Date string '" + value +
+        			"' contains an invalid date.");
+    }
+    
+    private static void checkTimeFormat(String value) {
+        if(value.length() > 0 && value.charAt(value.length()-1) == 'Z')
+            value = value.substring(0, value.length()-1);
+        
+        if(value.length() != 6)
+            throw new IllegalArgumentException("Time string '" + value +
+                    "' does not follow the format 'hhmmss' or 'hhmmssZ'.");
+            
+        // Time checking is not prepared for all special rules (for example
+        // leap seconds), production code could contain a full check.
+        
+        // if hour is 24, only 240000 should be allowed
+        checkNumber(value, "Time", 0, 2, 0, 24);
+        checkNumber(value, "Time", 2, 2, 0, 59);
+        checkNumber(value, "Time", 4, 2, 0, 59);
+        
+        if(value.startsWith("24") && !value.startsWith("240000"))
+        	throw new IllegalArgumentException("Time string is out of range.");
+    }
+    
+    private static int checkNumber(String value, String name, int from,
+            int length, int min, int max) {
+        String part = value.substring(from, from+length);
+        int number;
+        try {
+            number = Integer.parseInt(part);
+        } catch(NumberFormatException e) {
+            throw new IllegalArgumentException(name + " string '" + value +
+                    "' contains a non-numeric part.");
+        }
+        if(number < min || number > max)
+            throw new IllegalArgumentException("A segment of the " + name +
+                    " string '" + value + "' is out of range.");
+        
+        return number;
+    }
+
+    // character array of hexadecimal digits, used for printing binary data
+    private static char[] hex = "0123456789ABCDEF".toCharArray();
+
+    // generates a hexadecimal dump of the given binary data
+    private static String getHexDump(byte[] bytes) {
+        if (bytes.length == 0)
+            return "";
+
+        StringBuffer buf = new StringBuffer();
+        appendHexByte(buf, bytes[0]);
+        for (int i = 1; i < bytes.length; i++)
+            appendHexByte(buf.append(' '), bytes[i]);
+
+        return buf.toString();
+    }
+
+    private static void appendHexByte(StringBuffer buf, byte b) {
+        buf.append(hex[(b & 0xF0) >> 4]).append(hex[b & 0x0F]);
+    }
+    
+    private static String getFormatName(int format) {
+        return (String) FORMAT_NAMES.get(new Integer(format));
+    }
+}