FELIX-514 Updated compendium bundle to R4.1 OSGi API

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@681945 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/Uri.java b/org.osgi.compendium/src/main/java/info/dmtree/Uri.java
new file mode 100644
index 0000000..c9ad62c
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/Uri.java
@@ -0,0 +1,563 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/Uri.java,v 1.12 2006/10/24 17:54:28 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.io.UnsupportedEncodingException;
+import java.lang.reflect.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class contains static utility methods to manipulate DMT URIs.
+ * <p>
+ * Syntax of valid DMT URIs:
+ * <ul>
+ * <li>A slash (<code>'/'</code> &#92;u002F) is the separator of the node names.
+ * Slashes used in node name must therefore be escaped using a backslash slash 
+ * (<code>"\/"</code>). The backslash must be escaped with a double backslash 
+ * sequence. A backslash found must be ignored when it is not followed by a 
+ * slash or backslash.
+ * <li>The node name can be constructed using full Unicode character set
+ * (except the Supplementary code, not being supported by CLDC/CDC). However,
+ * using the full Unicode character set for node names is discouraged because
+ * the encoding in the underlying storage as well as the encoding needed in
+ * communications can create significant performance and memory usage overhead.
+ * Names that are restricted to the URI set <code>[-a-zA-Z0-9_.!~*'()]</code>
+ * are most efficient.
+ * <li>URIs used in the DMT must be treated and interpreted as case sensitive.
+ * <li>No End Slash: URI must not end with the delimiter slash (<code>'/'</code>
+ * &#92;u002F). This implies that the root node must be denoted as 
+ * <code>"."</code> and not <code>"./"</code>.
+ * <li>No parent denotation: URI must not be constructed using the character
+ * sequence <code>"../"</code> to traverse the tree upwards.
+ * <li>Single Root: The character sequence <code>"./"</code> must not be used
+ * anywhere else but in the beginning of a URI.
+ * </ul>
+ */
+public final class Uri {
+	/*
+	 * NOTE: An implementor may also choose to replace this class in
+	 * their distribution with a class that directly interfaces with the
+	 * info.dmtree implementation. This replacement class MUST NOT alter the
+	 * public/protected signature of this class.
+	 */
+
+	/*
+	 * This class will load the class named
+	 * by the org.osgi.vendor.dmtree.DigestDelegate property. This class will call
+	 * the public static byte[] digest(byte[]) method on that class.
+	 */
+	
+	private static class ImplHolder implements PrivilegedAction {
+		// the name of the system property containing the digest delegate class name
+		private static final String DIGEST_DELEGATE_PROPERTY = 
+			"org.osgi.vendor.dmtree.DigestDelegate";
+		
+		// the Method where message digest requests can be delegated
+		static final Method digestMethod;
+		
+		static {
+			digestMethod = (Method) AccessController.doPrivileged(new ImplHolder());
+		}
+		
+		private ImplHolder() {
+		}
+
+		public Object run() {
+			String className = System
+			.getProperty(DIGEST_DELEGATE_PROPERTY);
+			if (className == null) {
+				throw new NoClassDefFoundError("Digest " +
+						"delegate class property '" + 
+						DIGEST_DELEGATE_PROPERTY +
+						"' must be set to a " +
+						"class which implements a " +
+				"public static byte[] digest(byte[]) method."); 
+			}
+			
+			Class delegateClass;
+			try {
+				delegateClass = Class.forName(className);
+			}
+			catch (ClassNotFoundException e) {
+				throw new NoClassDefFoundError(e.toString());
+			}
+			
+			Method result;
+			try {
+				result = delegateClass.getMethod("digest",
+						new Class[] {byte[].class});
+			}
+			catch (NoSuchMethodException e) {
+				throw new NoSuchMethodError(e.toString());
+			}
+			
+			if (!Modifier.isStatic(result.getModifiers())) {
+				throw new NoSuchMethodError(
+				"digest method must be static");
+			}
+			
+			return result;
+		}
+	}
+
+
+    // the name of the system property containing the URI segment length limit 
+    private static final String SEGMENT_LENGTH_LIMIT_PROPERTY = 
+        "org.osgi.impl.service.dmt.uri.limits.segmentlength";
+    
+    // the smallest valid value for the URI segment length limit
+    private static final int MINIMAL_SEGMENT_LENGTH_LIMIT = 32;
+
+    // contains the maximum length of node names
+    private static final int segmentLengthLimit;
+
+    static {
+    	segmentLengthLimit = ((Integer) AccessController
+    	.doPrivileged(new PrivilegedAction() {
+    		public Object run() {
+    			String limitString = System.getProperty(SEGMENT_LENGTH_LIMIT_PROPERTY);
+    			int limit = MINIMAL_SEGMENT_LENGTH_LIMIT; // min. used as default
+    			
+    			try {
+    				int limitInt = Integer.parseInt(limitString);
+    				if(limitInt >= MINIMAL_SEGMENT_LENGTH_LIMIT)
+    					limit = limitInt;
+    			} catch(NumberFormatException e) {}
+    			
+    			return new Integer(limit);
+    		}
+    	})).intValue();
+    }
+    
+    // base64 encoding table, modified for use in node name mangling 
+    private static final char BASE_64_TABLE[] = {
+        'A','B','C','D','E','F','G','H',
+        'I','J','K','L','M','N','O','P',
+        'Q','R','S','T','U','V','W','X',
+        'Y','Z','a','b','c','d','e','f',
+        'g','h','i','j','k','l','m','n',
+        'o','p','q','r','s','t','u','v',
+        'w','x','y','z','0','1','2','3',
+        '4','5','6','7','8','9','+','_', // !!! this differs from base64
+    };
+
+    
+    /**
+     * A private constructor to suppress the default public constructor.
+     */
+    private Uri() {}
+    
+   /**
+     * Returns a node name that is valid for the tree operation methods, based
+     * on the given node name. This transformation is not idempotent, so it must
+     * not be called with a parameter that is the result of a previous
+     * <code>mangle</code> method call.
+     * <p>
+     * Node name mangling is needed in the following cases:
+     * <ul>
+     * <li>if the name contains '/' or '\' characters
+     * <li>if the length of the name exceeds the limit defined by the
+     * implementation
+     * </ul>
+     * <p>
+     * A node name that does not suffer from either of these problems is
+     * guaranteed to remain unchanged by this method. Therefore the client may
+     * skip the mangling if the node name is known to be valid (though it is
+     * always safe to call this method).
+     * <p>
+     * The method returns the normalized <code>nodeName</code> as described
+     * below. Invalid node names are normalized in different ways, depending on
+     * the cause. If the length of the name does not exceed the limit, but the
+     * name contains '/' or '\' characters, then these are simply escaped by
+     * inserting an additional '\' before each occurrence. If the length of the
+     * name does exceed the limit, the following mechanism is used to normalize
+     * it:
+     * <ul>
+     * <li>the SHA 1 digest of the name is calculated
+     * <li>the digest is encoded with the base 64 algorithm
+     * <li>all '/' characters in the encoded digest are replaced with '_'
+     * <li>trailing '=' signs are removed
+     * </ul>
+     * 
+     * @param nodeName the node name to be mangled (if necessary), must not be
+     *        <code>null</code> or empty
+     * @return the normalized node name that is valid for tree operations
+     * @throws NullPointerException if <code>nodeName</code> is
+     *         <code>null</code>
+     * @throws IllegalArgumentException if <code>nodeName</code> is empty
+     */
+    public static String mangle(String nodeName) {
+        return mangle(nodeName, getMaxSegmentNameLength());
+    }
+
+    /**
+     * Construct a URI from the specified URI segments. The segments must
+     * already be mangled.
+     * <p>
+     * If the specified path is an empty array then an empty URI 
+     * (<code>""</code>) is returned.
+     * 
+     * @param path a possibly empty array of URI segments, must not be 
+     *        <code>null</code>
+     * @return the URI created from the specified segments
+     * @throws NullPointerException if the specified path or any of its
+     *         segments are <code>null</code>
+     * @throws IllegalArgumentException if the specified path contains too many
+     *         or malformed segments or the resulting URI is too long
+     */
+    public static String toUri(String[] path) {
+        if (0 == path.length) {
+            return "";
+        }
+
+        if (path.length > getMaxUriSegments()) {
+            throw new IllegalArgumentException(
+                    "Path contains too many segments.");
+        }
+
+        StringBuffer uri = new StringBuffer();
+        int uriLength = 0;
+        for (int i = 0; i < path.length; ++i) {
+            // getSegmentLength throws exceptions on malformed segments.
+            int segmentLength = getSegmentLength(path[i]);
+            if (segmentLength > getMaxSegmentNameLength()) {
+                throw new IllegalArgumentException("URI segment too long.");
+            }
+            if (i > 0) {
+                uri.append('/');
+                uriLength++;
+            }
+            uriLength += segmentLength;
+            uri.append(path[i]);
+        }
+        if (uriLength > getMaxUriLength()) {
+            throw new IllegalArgumentException("URI too long.");
+        }
+        return uri.toString();
+    }
+
+    /**
+     * This method returns the length of a URI segment. The length of the URI
+     * segment is defined as the number of bytes in the unescaped, UTF-8 encoded
+     * represenation of the segment.
+     * <p>
+     * The method verifies that the URI segment is well-formed.
+     * 
+     * @param segment the URI segment
+     * @return URI segment length
+     * @throws NullPointerException if the specified segment is 
+     *         <code>null</code>
+     * @throws IllegalArgumentException if the specified URI segment is
+     *         malformed
+     */
+    private static int getSegmentLength(String segment) {
+        if (segment.length() == 0)
+            throw new IllegalArgumentException("URI segment is empty.");
+
+        StringBuffer newsegment = new StringBuffer(segment);
+        int i = 0;
+        while (i < newsegment.length()) { // length can decrease during the loop!
+            if (newsegment.charAt(i) == '\\') {
+                if (i == newsegment.length() - 1) // last character cannot be a '\'
+                    throw new IllegalArgumentException(
+                            "URI segment ends with the escape character.");
+
+                newsegment.deleteCharAt(i); // remove the extra '\'
+            } else if (newsegment.charAt(i) == '/')
+                throw new IllegalArgumentException(
+                        "URI segment contains an unescaped '/' character.");
+
+            i++;
+        }
+
+        if (newsegment.toString().equals(".."))
+            throw new IllegalArgumentException(
+                    "URI segment must not be \"..\".");
+
+        try {
+            return newsegment.toString().getBytes("UTF-8").length;
+        } catch (UnsupportedEncodingException e) {
+            // This should never happen. All implementations must support
+            // UTF-8 encoding;
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /**
+     * Split the specified URI along the path separator '/' charaters and return
+     * an array of URI segments. Special characters in the returned segments are
+     * escaped. The returned array may be empty if the specifed URI was empty.
+     * 
+     * @param uri the URI to be split, must not be <code>null</code>
+     * @return an array of URI segments created by splitting the specified URI
+     * @throws NullPointerException if the specified URI is <code>null</code>
+     * @throws IllegalArgumentException if the specified URI is malformed
+     */
+    public static String[] toPath(String uri) {
+        if (uri == null)
+            throw new NullPointerException("'uri' parameter is null.");
+
+        if (!isValidUri(uri))
+            throw new IllegalArgumentException("Malformed URI: " + uri);
+
+        if (uri.length() == 0)
+            return new String[] {};
+
+        List segments = new ArrayList();
+        StringBuffer segment = new StringBuffer();
+
+        boolean escape = false;
+        for (int i = 0; i < uri.length(); i++) {
+            char ch = uri.charAt(i);
+
+            if (escape) {
+                if(ch == '/' || ch == '\\')
+                    segment.append('\\');
+                segment.append(ch);
+                escape = false;
+            } else if (ch == '/') {
+                segments.add(segment.toString());
+                segment = new StringBuffer();
+            } else if (ch == '\\') {
+                escape = true;
+            } else
+                segment.append(ch);
+        }
+        if (segment.length() > 0) {
+            segments.add(segment.toString());
+        }
+
+        return (String[]) segments.toArray(new String[segments.size()]);
+    }
+
+    /**
+     * Returns the maximum allowed number of URI segments. The returned value is
+     * implementation specific.
+     * <p>
+     * The return value of <code>Integer.MAX_VALUE</code> indicates that there
+     * is no upper limit on the number of URI segments.
+     * 
+     * @return maximum number of URI segments supported by the implementation
+     */
+    public static int getMaxUriSegments() {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * Returns the maximum allowed length of a URI. The value is implementation
+     * specific. The length of the URI is defined as the number of bytes in the
+     * unescaped, UTF-8 encoded represenation of the URI.
+     * <p>
+     * The return value of <code>Integer.MAX_VALUE</code> indicates that there
+     * is no upper limit on the length of URIs.
+     * 
+     * @return maximum URI length supported by the implementation
+     */
+    public static int getMaxUriLength() {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * Returns the maximum allowed length of a URI segment. The value is
+     * implementation specific. The length of the URI segment is defined as the
+     * number of bytes in the unescaped, UTF-8 encoded represenation of the
+     * segment.
+     * <p>
+     * The return value of <code>Integer.MAX_VALUE</code> indicates that there
+     * is no upper limit on the length of segment names.
+     * 
+     * @return maximum URI segment length supported by the implementation
+     */
+    public static int getMaxSegmentNameLength() {
+        return segmentLengthLimit;
+    }
+
+    /**
+     * Checks whether the specified URI is an absolute URI. An absolute URI
+     * contains the complete path to a node in the DMT starting from the DMT
+     * root (".").
+     * 
+     * @param uri the URI to be checked, must not be <code>null</code> and must 
+     *        contain a valid URI
+     * @return whether the specified URI is absolute
+     * @throws NullPointerException if the specified URI is <code>null</code>
+     * @throws IllegalArgumentException if the specified URI is malformed
+     */
+    public static boolean isAbsoluteUri(String uri) {
+        if( null == uri ) {
+            throw new NullPointerException("'uri' parameter is null.");
+        }
+        if( !isValidUri(uri) ) 
+            throw new IllegalArgumentException("Malformed URI: " + uri);
+        return uri.equals(".") || uri.equals("\\.") || uri.startsWith("./") 
+                 || uri.startsWith("\\./");
+    }
+
+    /**
+     * Checks whether the specified URI is valid. A URI is considered valid if
+     * it meets the following constraints:
+     * <ul>
+     * <li>the URI is not <code>null</code>;
+     * <li>the URI follows the syntax defined for valid DMT URIs;
+     * <li>the length of the URI is not more than {@link #getMaxUriLength()};
+     * <li>the URI doesn't contain more than {@link #getMaxUriSegments()}
+     * segments;
+     * <li>the length of each segment of the URI is less than or equal to
+     * {@link #getMaxSegmentNameLength()}.
+     * </ul>
+     * The exact definition of the length of a URI and its segments is
+     * given in the descriptions of the <code>getMaxUriLength()</code> and
+     * <code>getMaxSegmentNameLength()</code> methods.  
+     * 
+     * @param uri the URI to be validated
+     * @return whether the specified URI is valid
+     */
+    public static boolean isValidUri(String uri) {
+        if (null == uri)
+            return false;
+        
+        int paramLen = uri.length();
+        if( paramLen == 0 ) 
+            return true;
+        if( uri.charAt(0) == '/' || uri.charAt(paramLen-1) == '\\' )
+            return false;
+        
+        int processedUriLength = 0;
+        int segmentNumber = 0;
+        
+        // append a '/' to indicate the end of the last segment (the URI in the
+        // parameter must not end with a '/')
+        uri += '/';
+        paramLen++;
+        
+        int start = 0;
+        for(int i = 1; i < paramLen; i++) { // first character is not a '/'
+            if(uri.charAt(i) == '/' && uri.charAt(i-1) != '\\') {
+                segmentNumber++;
+
+                String segment = uri.substring(start, i);
+                if(segmentNumber > 1 && segment.equals("."))
+                    return false; // the URI contains the "." node name at a 
+                                 // position other than the beginning of the URI
+                
+                int segmentLength;
+                try {
+                    // also checks that the segment is valid
+                    segmentLength = getSegmentLength(segment);
+                } catch(IllegalArgumentException e) {
+                    return false;
+                }
+                
+                if(segmentLength > getMaxSegmentNameLength())
+                    return false;
+                
+                // the extra byte is for the separator '/' (will be deducted
+                // again for the last segment of the URI)
+                processedUriLength += segmentLength + 1; 
+                start = i+1;
+            }
+        }
+        
+        processedUriLength--; // remove the '/' added to the end of the URI
+        
+        return segmentNumber <= getMaxUriSegments() &&  
+                processedUriLength <= getMaxUriLength();
+    }
+
+    // Non-public fields and methods
+
+    // package private method for testing purposes 
+    static String mangle(String nodeName, int limit) {
+        if(nodeName == null)
+            throw new NullPointerException(
+                    "The 'nodeName' parameter must not be null.");
+            
+        if(nodeName.equals(""))
+            throw new IllegalArgumentException(
+                    "The 'nodeName' parameter must not be empty.");        
+
+        if(nodeName.length() > limit)
+            // create node name hash
+            return getHash(nodeName);
+
+        // escape any '/' and '\' characters in the node name
+        StringBuffer nameBuffer = new StringBuffer(nodeName);
+        for(int i = 0; i < nameBuffer.length(); i++) // 'i' can increase in loop
+            if(nameBuffer.charAt(i) == '\\' || nameBuffer.charAt(i) == '/')
+                nameBuffer.insert(i++, '\\');
+        
+        return nameBuffer.toString();
+    }
+
+    private static String getHash(String from) {
+        byte[] byteStream;
+        try {
+            byteStream = from.getBytes("UTF-8");
+        }
+        catch (UnsupportedEncodingException e) {
+            // There's no way UTF-8 encoding is not implemented...
+            throw new IllegalStateException("there's no UTF-8 encoder here!");
+        }
+        byte[] digest = digestMessage(byteStream);
+        
+        // very dumb base64 encoder code. There is no need for multiple lines
+        // or trailing '='-s....
+        // also, we hardcoded the fact that sha-1 digests are 20 bytes long
+        StringBuffer sb = new StringBuffer(digest.length*2);
+        for(int i=0;i<6;i++) {
+            int d0 = digest[i*3]&0xff;
+            int d1 = digest[i*3+1]&0xff;
+            int d2 = digest[i*3+2]&0xff;
+            sb.append(BASE_64_TABLE[d0>>2]);
+            sb.append(BASE_64_TABLE[(d0<<4|d1>>4)&63]);
+            sb.append(BASE_64_TABLE[(d1<<2|d2>>6)&63]);
+            sb.append(BASE_64_TABLE[d2&63]);
+        }
+        int d0 = digest[18]&0xff;
+        int d1 = digest[19]&0xff;
+        sb.append(BASE_64_TABLE[d0>>2]);
+        sb.append(BASE_64_TABLE[(d0<<4|d1>>4)&63]);
+        sb.append(BASE_64_TABLE[(d1<<2)&63]);
+        
+        return sb.toString();
+    }
+    
+    private static byte[] digestMessage(byte[] byteStream) {
+		try {
+			try {
+				return (byte[]) ImplHolder.digestMethod.invoke(null, new Object[] {
+						byteStream});
+			}
+			catch (InvocationTargetException e) {
+				throw e.getTargetException();
+			}
+		}
+		catch (Error e) {
+			throw e;
+		}
+		catch (RuntimeException e) {
+			throw e;
+		}
+		catch (Throwable e) {
+			throw new RuntimeException(e.toString());
+		}
+    }
+}