blob: e277106bcb9f2be1089df5ebe046724cf8827d9b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.connect.felix.framework.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
public class Util
{
/**
* The default name used for the default configuration properties file.
*/
private static final String DEFAULT_PROPERTIES_FILE = "default.properties";
public static String getDefaultProperty(String name)
{
String value = null;
URL propURL = Util.class.getClassLoader().getResource(
DEFAULT_PROPERTIES_FILE);
if (propURL != null)
{
InputStream is = null;
try
{
// Load properties from URL.
is = propURL.openConnection().getInputStream();
Properties props = new Properties();
props.load(is);
is.close();
// Perform variable substitution for property.
value = props.getProperty(name);
value = (value != null) ? Util.substVars(value, name, null,
props) : null;
}
catch (Exception ex)
{
// Try to close input stream if we have one.
try
{
if (is != null)
{
is.close();
}
}
catch (IOException ex2)
{
// Nothing we can do.
}
ex.printStackTrace();
}
}
return value;
}
/**
* Converts a module identifier to a bundle identifier. Module IDs are typically
* {@code <bundle-id>.<revision>}; this method returns only the
* portion corresponding to the bundle ID.
*/
public static long getBundleIdFromModuleId(String id)
{
try
{
String bundleId = (id.indexOf('.') >= 0) ? id.substring(0,
id.indexOf('.')) : id;
return Long.parseLong(bundleId);
}
catch (NumberFormatException ex)
{
return -1;
}
}
/**
* Converts a module identifier to a bundle identifier. Module IDs are typically
* {@code <bundle-id>.<revision>}; this method returns only the
* portion corresponding to the revision.
*/
public static int getModuleRevisionFromModuleId(String id)
{
try
{
int index = id.indexOf('.');
if (index >= 0)
{
return Integer.parseInt(id.substring(index + 1));
}
}
catch (NumberFormatException ex)
{
}
return -1;
}
public static String getClassName(String className)
{
if (className == null)
{
className = "";
}
return (className.lastIndexOf('.') < 0) ? "" : className
.substring(className.lastIndexOf('.') + 1);
}
public static String getClassPackage(String className)
{
if (className == null)
{
className = "";
}
return (className.lastIndexOf('.') < 0) ? "" : className.substring(0,
className.lastIndexOf('.'));
}
public static String getResourcePackage(String resource)
{
if (resource == null)
{
resource = "";
}
// NOTE: The package of a resource is tricky to determine since
// resources do not follow the same naming conventions as classes.
// This code is pessimistic and assumes that the package of a
// resource is everything up to the last '/' character. By making
// this choice, it will not be possible to load resources from
// imports using relative resource names. For example, if a
// bundle exports "foo" and an importer of "foo" tries to load
// "/foo/bar/myresource.txt", this will not be found in the exporter
// because the following algorithm assumes the package name is
// "foo.bar", not just "foo". This only affects imported resources,
// local resources will work as expected.
String pkgName = (resource.startsWith("/")) ? resource.substring(1)
: resource;
pkgName = (pkgName.lastIndexOf('/') < 0) ? "" : pkgName.substring(0,
pkgName.lastIndexOf('/'));
pkgName = pkgName.replace('/', '.');
return pkgName;
}
/**
* <p>
* This is a simple utility class that attempts to load the named class using
* the class loader of the supplied class or the class loader of one of its
* super classes or their implemented interfaces. This is necessary during
* service registration to test whether a given service object implements its
* declared service interfaces.
* </p>
* <p>
* To perform this test, the framework must try to load the classes associated
* with the declared service interfaces, so it must choose a class loader. The
* class loader of the registering bundle cannot be used, since this disallows
* third parties to register service on behalf of another bundle. Consequently,
* the class loader of the service object must be used. However, this is also
* not sufficient since the class loader of the service object may not have
* direct access to the class in question.
* </p>
* <p>
* The service object's class loader may not have direct access to its service
* interface if it extends a super class from another bundle which implements
* the service interface from an imported bundle or if it implements an
* extension of the service interface from another bundle which imports the base
* interface from another bundle. In these cases, the service object's class
* loader only has access to the super class's class or the extended service
* interface, respectively, but not to the actual service interface.
* </p>
* <p>
* Thus, it is necessary to not only try to load the service interface class
* from the service object's class loader, but from the class loaders of any
* interfaces it implements and the class loaders of all super classes.
* </p>
*
* @param clazz the class that is the root of the search.
* @param name the name of the class to load.
* @return the loaded class or {@code null} if it could not be loaded.
*/
public static Class loadClassUsingClass(Class clazz, String name)
{
Class loadedClass = null;
while (clazz != null)
{
// Get the class loader of the current class object.
ClassLoader loader = clazz.getClassLoader();
// A null class loader represents the system class loader.
loader = (loader == null) ? ClassLoader.getSystemClassLoader()
: loader;
try
{
return loader.loadClass(name);
}
catch (ClassNotFoundException ex)
{
// Ignore and try interface class loaders.
}
// Try to see if we can load the class from
// one of the class's implemented interface
// class loaders.
Class[] ifcs = clazz.getInterfaces();
for (int i = 0; i < ifcs.length; i++)
{
loadedClass = loadClassUsingClass(ifcs[i], name);
if (loadedClass != null)
{
return loadedClass;
}
}
// Try to see if we can load the class from
// the super class class loader.
clazz = clazz.getSuperclass();
}
return null;
}
/**
* This method determines if the requesting bundle is able to cast the specified
* service reference based on class visibility rules of the underlying modules.
*
* @param requester The bundle requesting the service.
* @param ref The service in question.
* @return {@code true} if the requesting bundle is able to case the service
* object to a known type.
*/
public static boolean isServiceAssignable(Bundle requester,
ServiceReference ref)
{
// Boolean flag.
boolean allow = true;
// Get the service's objectClass property.
String[] objectClass = (String[]) ref
.getProperty(Constants.OBJECTCLASS);
// The the service reference is not assignable when the requesting
// bundle is wired to a different version of the service object.
// NOTE: We are pessimistic here, if any class in the service's
// objectClass is not usable by the requesting bundle, then we
// disallow the service reference.
for (int classIdx = 0; (allow) && (classIdx < objectClass.length); classIdx++)
{
if (!ref.isAssignableTo(requester, objectClass[classIdx]))
{
allow = false;
}
}
return allow;
}
private static final byte encTab[] = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51,
0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62,
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d,
0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x2b, 0x2f};
private static final byte decTab[] = {-1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
-1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1};
public static String base64Encode(String s) throws IOException
{
return encode(s.getBytes(), 0);
}
/**
* Encode a raw byte array to a Base64 String.
*
* @param in Byte array to encode.
* @param len Length of Base64 lines. 0 means no line breaks.
*/
public static String encode(byte[] in, int len) throws IOException
{
ByteArrayOutputStream baos = null;
ByteArrayInputStream bais = null;
try
{
baos = new ByteArrayOutputStream();
bais = new ByteArrayInputStream(in);
encode(bais, baos, len);
// ASCII byte array to String
return (new String(baos.toByteArray()));
}
finally
{
if (baos != null)
{
baos.close();
}
if (bais != null)
{
bais.close();
}
}
}
public static void encode(InputStream in, OutputStream out, int len)
throws IOException
{
// Check that length is a multiple of 4 bytes
if (len % 4 != 0)
{
throw new IllegalArgumentException("Length must be a multiple of 4");
}
// Read input stream until end of file
int bits = 0;
int nbits = 0;
int nbytes = 0;
int b;
while ((b = in.read()) != -1)
{
bits = (bits << 8) | b;
nbits += 8;
while (nbits >= 6)
{
nbits -= 6;
out.write(encTab[0x3f & (bits >> nbits)]);
nbytes++;
// New line
if (len != 0 && nbytes >= len)
{
out.write(0x0d);
out.write(0x0a);
nbytes -= len;
}
}
}
switch (nbits)
{
case 2:
out.write(encTab[0x3f & (bits << 4)]);
out.write(0x3d); // 0x3d = '='
out.write(0x3d);
break;
case 4:
out.write(encTab[0x3f & (bits << 2)]);
out.write(0x3d);
break;
}
if (len != 0)
{
if (nbytes != 0)
{
out.write(0x0d);
out.write(0x0a);
}
out.write(0x0d);
out.write(0x0a);
}
}
private static final String DELIM_START = "${";
private static final String DELIM_STOP = "}";
/**
* <p>
* This method performs property variable substitution on the specified value.
* If the specified value contains the syntax {@code ${&lt;prop-name&gt;}},
* where {@code &lt;prop-name&gt;} refers to either a configuration property or
* a system property, then the corresponding property value is substituted for
* the variable placeholder. Multiple variable placeholders may exist in the
* specified value as well as nested variable placeholders, which are
* substituted from inner most to outer most. Configuration properties override
* system properties.
* </p>
*
* @param val The string on which to perform property substitution.
* @param currentKey The key of the property being evaluated used to detect
* cycles.
* @param cycleMap Map of variable references used to detect nested cycles.
* @param configProps Set of configuration properties.
* @return The value of the specified string after system property substitution.
* @throws IllegalArgumentException If there was a syntax error in the property
* placeholder syntax or a recursive variable
* reference.
*/
public static String substVars(String val, String currentKey, Map cycleMap,
Properties configProps) throws IllegalArgumentException
{
// If there is currently no cycle map, then create
// one for detecting cycles for this invocation.
if (cycleMap == null)
{
cycleMap = new HashMap();
}
// Put the current key in the cycle map.
cycleMap.put(currentKey, currentKey);
// Assume we have a value that is something like:
// "leading ${foo.${bar}} middle ${baz} trailing"
// Find the first ending '}' variable delimiter, which
// will correspond to the first deepest nested variable
// placeholder.
int stopDelim = -1;
int startDelim = -1;
do
{
stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1);
// If there is no stopping delimiter, then just return
// the value since there is no variable declared.
if (stopDelim < 0)
{
return val;
}
// Try to find the matching start delimiter by
// looping until we find a start delimiter that is
// greater than the stop delimiter we have found.
startDelim = val.indexOf(DELIM_START);
// If there is no starting delimiter, then just return
// the value since there is no variable declared.
if (startDelim < 0)
{
return val;
}
while (stopDelim >= 0)
{
int idx = val.indexOf(DELIM_START,
startDelim + DELIM_START.length());
if ((idx < 0) || (idx > stopDelim))
{
break;
}
else if (idx < stopDelim)
{
startDelim = idx;
}
}
}
while ((startDelim > stopDelim) && (stopDelim >= 0));
// At this point, we have found a variable placeholder so
// we must perform a variable substitution on it.
// Using the start and stop delimiter indices, extract
// the first, deepest nested variable placeholder.
String variable = val.substring(startDelim + DELIM_START.length(),
stopDelim);
// Verify that this is not a recursive variable reference.
if (cycleMap.get(variable) != null)
{
throw new IllegalArgumentException("recursive variable reference: "
+ variable);
}
// Get the value of the deepest nested variable placeholder.
// Try to configuration properties first.
String substValue = (configProps != null) ? configProps.getProperty(
variable, null) : null;
if (substValue == null)
{
// Ignore unknown property values.
substValue = System.getProperty(variable, "");
}
// Remove the found variable from the cycle map, since
// it may appear more than once in the value and we don't
// want such situations to appear as a recursive reference.
cycleMap.remove(variable);
// Append the leading characters, the substituted value of
// the variable, and the trailing characters to get the new
// value.
val = val.substring(0, startDelim) + substValue
+ val.substring(stopDelim + DELIM_STOP.length(), val.length());
// Now perform substitution again, since there could still
// be substitutions to make.
val = substVars(val, currentKey, cycleMap, configProps);
// Return the value.
return val;
}
public static List<String> parseDelimitedString(String value, String delim)
{
return parseDelimitedString(value, delim, true);
}
/**
* Parses delimited string and returns an array containing the tokens. This
* parser obeys quotes, so the delimiter character will be ignored if it is
* inside of a quote. This method assumes that the quote character is not
* included in the set of delimiter characters.
* @param value the delimited string to parse.
* @param delim the characters delimiting the tokens.
* @return a list of string or an empty list if there are none.
**/
public static List<String> parseDelimitedString(String value, String delim, boolean trim)
{
if (value == null)
{
value = "";
}
List<String> list = new ArrayList<String>();
int CHAR = 1;
int DELIMITER = 2;
int STARTQUOTE = 4;
int ENDQUOTE = 8;
StringBuilder sb = new StringBuilder();
int expecting = (CHAR | DELIMITER | STARTQUOTE);
boolean isEscaped = false;
for (int i = 0; i < value.length(); i++)
{
char c = value.charAt(i);
boolean isDelimiter = (delim.indexOf(c) >= 0);
if (!isEscaped && (c == '\\'))
{
isEscaped = true;
continue;
}
if (isEscaped)
{
sb.append(c);
}
else if (isDelimiter && ((expecting & DELIMITER) > 0))
{
if (trim)
{
list.add(sb.toString().trim());
}
else
{
list.add(sb.toString());
}
sb.delete(0, sb.length());
expecting = (CHAR | DELIMITER | STARTQUOTE);
}
else if ((c == '"') && ((expecting & STARTQUOTE) > 0))
{
sb.append(c);
expecting = CHAR | ENDQUOTE;
}
else if ((c == '"') && ((expecting & ENDQUOTE) > 0))
{
sb.append(c);
expecting = (CHAR | STARTQUOTE | DELIMITER);
}
else if ((expecting & CHAR) > 0)
{
sb.append(c);
}
else
{
throw new IllegalArgumentException("Invalid delimited string: " + value);
}
isEscaped = false;
}
if (sb.length() > 0)
{
if (trim)
{
list.add(sb.toString().trim());
}
else
{
list.add(sb.toString());
}
}
return list;
}
private static final List EMPTY_LIST = Collections.unmodifiableList(Collections.EMPTY_LIST);
private static final Map EMPTY_MAP = Collections.unmodifiableMap(Collections.EMPTY_MAP);
public static <T> List<T> newImmutableList(List<T> list)
{
return list == null || list.isEmpty() ? EMPTY_LIST : Collections.unmodifiableList(list);
}
public static <K,V> Map<K,V> newImmutableMap(Map<K,V> map)
{
return map == null || map.isEmpty() ? EMPTY_MAP : Collections.unmodifiableMap(map);
}
}