blob: 9ce68649227fd8dda2db95abc85e67300b8b30c8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.framework.internal.core;
import java.util.Enumeration;
import java.util.Vector;
import org.eclipse.osgi.service.resolver.VersionRange;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.*;
import org.osgi.framework.Constants;
/**
* This class represents a description of native code.
*
* Native Code dependencies
*
* The Bundle-NativeCode header allows a bundle to carry the native code it
* needs, and make use of it when it is installed. The bundle must have
* RuntimePermission in order to run native code in the Framework. The value of
* the header must conform to the following syntax:
*
*
* Bundle-NativeCode: nativecode-clause ( , nativecode-clause)*
* nativecode-clause: nativepaths ( ; env-parameter )* nativepaths: nativepath ( ;
* nativepath )* env-parameter: ( processordef | osnamedef | osversiondef |
* languagedef ) processordef: <I>processor= </I>token osnamedef: <I>osname=
* </I>token osversiondef: <I>osversion= </I>token languagedef: <I>language=
* </I>token
*
* For example:
*
* Bundle-NativeCode: http.dll ; osname=Win95; processor=x86; language=en,
* libhttp.so; osname=Solaris; processor=sparc
*
* The Bundle-NativeCode header allows a bundle programmer to specify an
* environment, and to declare what native code libraries it carries for that
* specific environment. The environment is characterized by the following
* properties:
*
* <UL>
* <LI><CODE>processor</CODE> The processor on which the hosting the
* Framework is running. It is compared against <CODE>
* org.osgi.framework.processor</CODE>.
* <LI><CODE>osname</CODE> The operating system name. It is compared against
* <CODE>org.osgi.framework.os.name</CODE>.
* <LI><CODE>selection-filter</CODE> An optional filter that can be used to
* match against system properties. If the filter does not match then the
* native code clause will not be selected.
* <LI><CODE>osversion</CODE> The version of the operating system. It is
* compared against <CODE>org.osgi.framework.os.version</CODE>.
* <LI><CODE>language</CODE> The language. It is compared against <CODE>
* org.osgi.framework.language</CODE>.
* </UL>
*
* These properties should follow the conventions and values defined in the
* "Open Software Description" specification.
*
* The Framework uses the following algorithm to find the "best" matching
* native code clause:
*
* <ol>
* <li>Pick the clauses with a matching processor and operating system with
* the one the Framework runs on. If no clause matches both the required
* processor and operating system, the bundle installation/activation fails. If
* only one clause matches, it can be used, otherwise, remaining steps are
* executed.</li>
* <li>Pick the clauses that best match the operating system version. If they
* match each other exactly, that clause is considered the best match. If there
* is only one clause with an exact match, it can be used. If there are more
* than one clause that matches the property, these clauses will be picked to
* perform the next step. Operating system versions are taken to be backward
* compatible. If there is no exact match in the clauses, clauses with
* operating system versions lower than the value specified in
* org.osgi.framework.osversion will be picked. If there is only one clause
* which has a compatible operating system version, it can be used. Otherwise,
* all clauses with compatible operating system versions will go through the
* next step. If no clause has a matching or compatible operating system
* version, pick the clause that does not have operating system version
* specified. If that is not possible, the bundle installation fails.</li>
* <li>Pick the clause that best matches the language. If more than one clause
* remains at that point, then the Framework is free to pick amongst them
* randomly. If no clauses have the exact match with the value of the property,
* pick the clause that does not have language specified. If that is not
* possible, the bundle installation fails.</li>
* </ol>
*
*/
public class BundleNativeCode {
/**
* The Native Code paths for the Native Code entry
*/
private Attribute nativepaths;
/**
* The processor attribute for this Native Code entry
*/
private Attribute processor;
/**
* The osname attribute for this Native Code entry
*/
private Attribute osname;
/**
* The language attribute for this Native Code entry
*/
private Attribute language;
/**
* The osversion attribute for this Native Code entry
*/
private Attribute osversion;
/**
* The filter attribute for this Native Code entry
*/
private String filterString;
/**
* The Framework for this BundleNativeCode
*/
private AbstractBundle bundle;
/**
* The AliasMapper used to alias OS Names.
*/
private static AliasMapper aliasMapper = Framework.aliasMapper;
/**
* Constructor for BundleNativeCode. It reads bundle native code data from
* the manifest file.
*
*/
protected BundleNativeCode(ManifestElement element, AbstractBundle bundle) {
this.bundle = bundle;
String[] nativePaths = element.getValueComponents();
for (int i = 0; i < nativePaths.length; i++) {
addPath(nativePaths[i]);
}
setAttribute(element, Constants.BUNDLE_NATIVECODE_OSNAME);
setAttribute(element, Constants.BUNDLE_NATIVECODE_PROCESSOR);
setAttribute(element, Constants.BUNDLE_NATIVECODE_OSVERSION);
setAttribute(element, Constants.BUNDLE_NATIVECODE_LANGUAGE);
setAttribute(element, Constants.SELECTION_FILTER_ATTRIBUTE);
}
private void setAttribute(ManifestElement element, String attribute) {
String[] attrValues = element.getAttributes(attribute);
if (attrValues != null) {
for (int i = 0; i < attrValues.length; i++) {
addAttribute(attribute, attrValues[i]);
}
}
}
/**
* Returns the native code paths.
*
* @return Vector of String code paths.
*/
public String[] getPaths() {
if (nativepaths == null) {
return null;
}
String[] paths = new String[nativepaths.size()];
nativepaths.toArray(paths);
return (paths);
}
/**
* addPath is used to add a new element to the list of native files.
*
* @param nativepath
* new native file
*/
protected void addPath(String nativepath) {
if (nativepaths == null) {
nativepaths = new Attribute();
}
nativepaths.addElement(nativepath);
}
/**
* addAttribute is used to add the specification-version string to the
* package description. It is the only key supported at this time.
*
* @param key
* attribute key name
* @param value
* attribute value name
*/
protected synchronized void addAttribute(String key, String value) {
if (key.equals(Constants.BUNDLE_NATIVECODE_PROCESSOR)) {
if (processor == null) {
processor = new Attribute();
}
processor.addElement(aliasMapper.aliasProcessor(value));
return;
}
if (key.equals(Constants.BUNDLE_NATIVECODE_OSNAME)) {
if (osname == null) {
osname = new Attribute();
}
osname.addElement(aliasMapper.aliasOSName(value));
return;
}
if (key.equals(Constants.BUNDLE_NATIVECODE_OSVERSION)) {
if (osversion == null) {
osversion = new Attribute();
}
osversion.addElement(new VersionRange(value));
return;
}
if (key.equals(Constants.SELECTION_FILTER_ATTRIBUTE)) {
if (filterString == null) {
filterString = value;
}
return;
}
if (key.equals(Constants.BUNDLE_NATIVECODE_LANGUAGE)) {
if (language == null) {
language = new Attribute();
}
language.addElement(value.toLowerCase());
return;
}
}
/**
* Override toString. Return a String representation of this object
*
* @return String representation of the object
*/
public String toString() {
int size = nativepaths.size();
StringBuffer sb = new StringBuffer(50 * size);
for (int i = 0; i < size; i++) {
if (i > 0) {
sb.append(';');
}
sb.append(nativepaths.elementAt(i).toString());
}
if (processor != null) {
size = processor.size();
for (int i = 0; i < size; i++) {
sb.append(';');
sb.append(Constants.BUNDLE_NATIVECODE_PROCESSOR);
sb.append('=');
sb.append(processor.elementAt(i).toString());
}
}
if (osname != null) {
size = osname.size();
for (int i = 0; i < size; i++) {
sb.append(';');
sb.append(Constants.BUNDLE_NATIVECODE_OSNAME);
sb.append('=');
sb.append(osname.elementAt(i).toString());
}
}
if (osversion != null) {
size = osversion.size();
for (int i = 0; i < size; i++) {
sb.append(';');
sb.append(Constants.BUNDLE_NATIVECODE_OSVERSION);
sb.append('=');
sb.append(osversion.elementAt(i).toString());
}
}
if (language != null) {
size = language.size();
for (int i = 0; i < size; i++) {
sb.append(';');
sb.append(Constants.BUNDLE_NATIVECODE_LANGUAGE);
sb.append('=');
sb.append(language.elementAt(i).toString());
}
}
return (sb.toString());
}
/**
* Return the match value for the given processor and os name. A higher
* value indicates a better match.
*
* @param processor
* processor name to match against.
* @param osname
* os name to match against.
* @return match value
*/
public int matchProcessorOSNameFilter(String processor, String osname) {
if ((this.processor == null) || (this.osname == null)) {
return (0);
}
String otherProcessor = aliasMapper.aliasProcessor(processor);
String otherOSName = (String) aliasMapper.aliasOSName(osname);
if (this.processor.equals(otherProcessor) && this.osname.equals(otherOSName) && matchFilter()) {
return (1);
}
return (0);
}
/**
* Return the higest matching value for the given os version that is less
* than or equal to the given os version.
*
* @param version
* os version to match against.
* @return version or null if no match.
*/
public Version matchOSVersion(Version version) {
if (this.osversion == null)
return Version.emptyVersion;
Version result = null;
int size = this.osversion.size();
for (int i = 0; i < size; i++) {
// find a matching range and save the highest result
VersionRange range = (VersionRange) this.osversion.elementAt(i);
if (range.isIncluded(version) && (result == null || (range.getMinimum().compareTo(result) > 0)))
result = range.getMinimum();
}
return result;
}
/**
* Return the match value for the given language. A higher value indicates
* a better match.
*
* @param language
* language name to match against.
* @return match value
*/
public int matchLanguage(String language) {
if (this.language == null) {
return (1);
}
if (this.language.equals(language.toLowerCase())) {
return (2);
}
return (0);
}
public boolean matchFilter() {
if (filterString == null) {
return true;
}
FilterImpl filter;
try {
filter = new FilterImpl(filterString);
} catch (InvalidSyntaxException e) {
BundleException be = new BundleException(Msg.BUNDLE_NATIVECODE_INVALID_FILTER, e);
bundle.framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundle, be);
return false;
}
return filter.match(FrameworkProperties.getProperties());
}
/**
* Extension of Vector for attributes.
*/
static class Attribute extends Vector {
private static final long serialVersionUID = 3257005440914174512L;
/**
* Attribute constructor.
*
*/
Attribute() {
super(10, 10);
}
/**
* Perform an "OR" operation on equals.
*
* @param obj
* Object to test against.
* @return true if at least one attribute is equal; false otherwise.
*/
public synchronized boolean equals(Object obj) {
for (int i = 0; i < elementCount; i++) {
Object data = elementData[i];
if (data instanceof String) {
if (elementData[i].equals(obj)) {
return (true);
}
} else {
Enumeration e = ((Vector) data).elements();
while (e.hasMoreElements()) {
if (((String) e.nextElement()).equals(obj)) {
return true;
}
}
}
}
return (false);
}
/**
* Add the object if it is not already in the vector.
*
* @param obj
* Object to add to the vector.
*/
public synchronized void addElement(Object obj) {
if (!contains(obj)) {
super.addElement(obj);
}
}
}
}