FELIX-20 Implement Metatype Service (Initial Checkin)
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@527597 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/metatype/pom.xml b/metatype/pom.xml
new file mode 100644
index 0000000..c0e2eb1
--- /dev/null
+++ b/metatype/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project>
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix</artifactId>
+ <version>0.9.0-incubator-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.apache.felix.metatype</artifactId>
+ <packaging>bundle</packaging>
+
+ <name>Apache Felix Metatype Service</name>
+ <description>
+ Implementation of the OSGi Metatype Service Specification 1.1
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.kxml</groupId>
+ <artifactId>kxml2</artifactId>
+ <version>2.2.2</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>0.9.0-incubator-SNAPSHOT</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Category>osgi</Bundle-Category>
+ <Export-Package>
+ org.apache.felix.metatype,
+ org.osgi.service.metatype; version=1.1
+ </Export-Package>
+ <Private-Package>
+ org.apache.felix.metatype.internal,
+ org.apache.felix.metatype.internal.l10n,
+ org.kxml2.io,
+ org.xmlpull.v1
+ </Private-Package>
+ <Bundle-Activator>
+ org.apache.felix.metatype.internal.Activator
+ </Bundle-Activator>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/metatype/src/main/java/org/apache/felix/metatype/AD.java b/metatype/src/main/java/org/apache/felix/metatype/AD.java
new file mode 100644
index 0000000..5ec7912
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/AD.java
@@ -0,0 +1,480 @@
+/*
+ * 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.metatype;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.metatype.internal.Activator;
+import org.osgi.service.log.LogService;
+import org.osgi.service.metatype.AttributeDefinition;
+
+
+/**
+ * The <code>AD</code> class represents the <code>AD</code> element of the
+ * meta type descriptor.
+ *
+ * @author fmeschbe
+ */
+public class AD
+{
+
+ /**
+ * The message returned from the {@link #validate(String)} method if the
+ * value is not any of the specified {@link #getOptionValues() option values}
+ * (value is "%not a valid option").
+ */
+ public static final String VALIDATE_NOT_A_VALID_OPTION = "%not a valid option";
+
+ /**
+ * The message returned from the {@link #validate(String)} method if the
+ * value is greater than the specified {@link #getMax() maximum value}
+ * (value is "%greater than maximum").
+ */
+ public static final String VALIDATE_GREATER_THAN_MAXIMUM = "%greater than maximum";
+
+ /**
+ * The message returned from the {@link #validate(String)} method if the
+ * value is less than the specified {@link #getMin() minimum value}
+ * (value is "%less than minimum").
+ */
+ public static final String VALIDATE_LESS_THAN_MINIMUM = "%less than minimum";
+
+ private String id;
+ private String name;
+ private String description;
+ private int type;
+ private int cardinality = 0;
+ private String[] optionLabels;
+ private String[] optionValues;
+ private String[] defaultValue;
+ private String min;
+ private String max;
+ private boolean isRequired = true;
+
+
+ public String getID()
+ {
+ return id;
+ }
+
+
+ public String getName()
+ {
+ return name;
+ }
+
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+
+ public int getType()
+ {
+ return type;
+ }
+
+
+ public int getCardinality()
+ {
+ return cardinality;
+ }
+
+
+ public String[] getOptionLabels()
+ {
+ return optionLabels;
+ }
+
+
+ public String[] getOptionValues()
+ {
+ return optionValues;
+ }
+
+
+ public String[] getDefaultValue()
+ {
+ return defaultValue;
+ }
+
+
+ public String getMin()
+ {
+ return min;
+ }
+
+
+ public String getMax()
+ {
+ return max;
+ }
+
+
+ public boolean isRequired()
+ {
+ return isRequired;
+ }
+
+
+ /**
+ * Implements validation of the <code>valueString</code> and returns an
+ * indication of the success:
+ * <dl>
+ * <dt><code>null</code>
+ * <dd>If neither a {@link #getMin() minimal value} nor a
+ * {@link #getMax() maximal value} nor any
+ * {@link #getOptionValues() optional values} are defined in this
+ * instance, validation cannot be performed.
+ * <dt>Empty String
+ * <dd>If validation succeeds. This value is also returned if the
+ * <code>valueString</code> is empty or <code>null</code> or cannot be
+ * converted into a numeric type.
+ * <dt><b>%</b>message
+ * <dd>If the value falls below the minimum, higher than the maximum or is
+ * not any of the option values, an explanatory message, which may be
+ * localized is returned. If any of the minimum, maximum or option
+ * values is <code>null</code>, the respective value is not checked.
+ * </dl>
+ *
+ * @param valueString The string representation of the value to validate.
+ *
+ * @return As explained above.
+ *
+ * @see #VALIDATE_GREATER_THAN_MAXIMUM
+ * @see #VALIDATE_LESS_THAN_MINIMUM
+ * @see #VALIDATE_NOT_A_VALID_OPTION
+ */
+ public String validate( String valueString )
+ {
+ // no validation if no min and max
+ if ( getMin() == null && getMax() == null && getOptionValues() == null )
+ {
+ return null;
+ }
+
+ Comparable value = convertToType( valueString );
+ if ( value == null )
+ {
+ return ""; // accept null value
+ }
+
+ Comparable other = convertToType( getMin() );
+ if ( other != null )
+ {
+ if ( value.compareTo( other ) < 0 )
+ {
+ return VALIDATE_LESS_THAN_MINIMUM;
+ }
+ }
+
+ other = convertToType( getMax() );
+ if ( other != null )
+ {
+ if ( value.compareTo( other ) > 0 )
+ {
+ return VALIDATE_GREATER_THAN_MAXIMUM;
+ }
+ }
+
+ String[] optionValues = getOptionValues();
+ if ( optionValues != null )
+ {
+ for ( int i = 0; i < optionValues.length; i++ )
+ {
+ other = convertToType( optionValues[i] );
+ if ( value.compareTo( other ) == 0 )
+ {
+ // one of the option values
+ return "";
+ }
+ }
+
+ // not any of the option values, fail
+ return VALIDATE_NOT_A_VALID_OPTION;
+ }
+
+ // finally, we accept the value
+ return "";
+ }
+
+
+ //--------- Setters for setting up this instance --------------------------
+
+ /**
+ * @param id the id to set
+ */
+ public void setID( String id )
+ {
+ this.id = id;
+ }
+
+
+ /**
+ * @param name the name to set
+ */
+ public void setName( String name )
+ {
+ this.name = name;
+ }
+
+
+ /**
+ * @param description the description to set
+ */
+ public void setDescription( String description )
+ {
+ this.description = description;
+ }
+
+
+ /**
+ * @param type the type to set
+ */
+ public void setType( String typeString )
+ {
+ this.type = toType( typeString );
+ }
+
+
+ /**
+ * @param cardinality the cardinality to set
+ */
+ public void setCardinality( int cardinality )
+ {
+ this.cardinality = cardinality;
+ }
+
+
+ /**
+ * @param optionLabels the optionLabels to set
+ */
+ public void setOptions( Map options )
+ {
+ optionLabels = new String[options.size()];
+ optionValues = new String[options.size()];
+ int i = 0;
+ for ( Iterator oi = options.entrySet().iterator(); oi.hasNext(); i++ )
+ {
+ Map.Entry entry = ( Map.Entry ) oi.next();
+ optionValues[i] = String.valueOf( entry.getKey() );
+ optionLabels[i] = String.valueOf( entry.getValue() );
+ }
+ }
+
+
+ /**
+ * @param defaultValue the defaultValue to set
+ */
+ public void setDefaultValue( String defaultValue )
+ {
+ this.defaultValue = splitList( defaultValue );
+ }
+
+
+ /**
+ * @param min the min to set
+ */
+ public void setMin( String min )
+ {
+ this.min = min;
+ }
+
+
+ /**
+ * @param max the max to set
+ */
+ public void setMax( String max )
+ {
+ this.max = max;
+ }
+
+
+ /**
+ * @param defaultValue the defaultValue to set
+ */
+ public void setDefaultValue( String[] defaultValue )
+ {
+ this.defaultValue = ( String[] ) defaultValue.clone();
+ }
+
+
+ /**
+ * @param isRequired the isRequired to set
+ */
+ public void setRequired( boolean isRequired )
+ {
+ this.isRequired = isRequired;
+ }
+
+
+ public static int toType( String typeString )
+ {
+ if ( "String".equals( typeString ) )
+ {
+ return AttributeDefinition.STRING;
+ }
+ else if ( "Long".equals( typeString ) )
+ {
+ return AttributeDefinition.LONG;
+ }
+ else if ( "Double".equals( typeString ) )
+ {
+ return AttributeDefinition.DOUBLE;
+ }
+ else if ( "Float".equals( typeString ) )
+ {
+ return AttributeDefinition.FLOAT;
+ }
+ else if ( "Integer".equals( typeString ) )
+ {
+ return AttributeDefinition.INTEGER;
+ }
+ else if ( "Byte".equals( typeString ) )
+ {
+ return AttributeDefinition.BYTE;
+ }
+ else if ( "Char".equals( typeString ) )
+ {
+ return AttributeDefinition.CHARACTER;
+ }
+ else if ( "Boolean".equals( typeString ) )
+ {
+ return AttributeDefinition.BOOLEAN;
+ }
+ else if ( "Short".equals( typeString ) )
+ {
+ return AttributeDefinition.SHORT;
+ }
+
+ // finally fall back to string for illegal values
+ return AttributeDefinition.STRING;
+ }
+
+
+ public static String[] splitList( String listString )
+ {
+ // return nothing ...
+ if ( listString == null )
+ {
+ return null;
+ }
+
+ List values = new ArrayList();
+ boolean escape = false;
+ StringBuffer buf = new StringBuffer();
+ for ( int i = 0; i < listString.length(); i++ )
+ {
+ char c = listString.charAt( i );
+
+ if ( escape )
+ {
+ // just go ahead
+ escape = false;
+ }
+ else if ( c == ',' )
+ {
+ String value = buf.toString().trim();
+ if ( value.length() > 0 )
+ {
+ values.add( value );
+ }
+ buf.delete( 0, buf.length() );
+ continue;
+ }
+ else if ( c == '\\' )
+ {
+ escape = true;
+ continue;
+ }
+
+ buf.append( c );
+ }
+
+ // add last string
+ if ( buf.length() > 0 )
+ {
+ String value = buf.toString().trim();
+ if ( value.length() > 0 )
+ {
+ values.add( value );
+ }
+ }
+
+ return values.isEmpty() ? null : ( String[] ) values.toArray( new String[values.size()] );
+ }
+
+
+ protected Comparable convertToType( final String value )
+ {
+ if ( value != null && value.length() > 0 )
+ {
+ try
+ {
+ switch ( getType() )
+ {
+ case AttributeDefinition.BOOLEAN:
+ // Boolean is only Comparable starting with Java 5
+ return new ComparableBoolean(value);
+ case AttributeDefinition.CHARACTER:
+ return new Character( value.charAt( 0 ) );
+ case AttributeDefinition.BYTE:
+ return Byte.valueOf( value );
+ case AttributeDefinition.SHORT:
+ return Short.valueOf( value );
+ case AttributeDefinition.INTEGER:
+ return Integer.valueOf( value );
+ case AttributeDefinition.LONG:
+ return Long.valueOf( value );
+ case AttributeDefinition.FLOAT:
+ return Float.valueOf( value );
+ case AttributeDefinition.DOUBLE:
+ return Double.valueOf( value );
+ case AttributeDefinition.STRING:
+ default:
+ return value;
+ }
+ }
+ catch ( NumberFormatException nfe )
+ {
+ Activator.log( LogService.LOG_INFO, "Cannot convert value '" + value + "'", nfe );
+ }
+ }
+
+ return null;
+ }
+
+ private static class ComparableBoolean implements Comparable {
+ private boolean value;
+
+ ComparableBoolean(String boolValue) {
+ value = Boolean.valueOf(boolValue).booleanValue();
+ }
+
+ public int compareTo(Object obj) {
+ ComparableBoolean cb = (ComparableBoolean) obj;
+ return (cb.value == value ? 0 : (value ? 1 : -1));
+ }
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/Attribute.java b/metatype/src/main/java/org/apache/felix/metatype/Attribute.java
new file mode 100644
index 0000000..81a5bc1
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/Attribute.java
@@ -0,0 +1,78 @@
+/*
+ * 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.metatype;
+
+
+/**
+ * The <code>Attribute</code> TODO
+ *
+ * @author fmeschbe
+ * @version $Rev:$, $Date:$
+ */
+public class Attribute
+{
+
+ private String adRef;
+ private String[] content;
+
+
+ public String getAdRef()
+ {
+ return adRef;
+ }
+
+
+ public void setAdRef( String adRef )
+ {
+ this.adRef = adRef;
+ }
+
+
+ public String[] getContent()
+ {
+ return ( String[] ) content.clone();
+ }
+
+
+ public void addContent( String[] added )
+ {
+ if ( added != null && added.length > 0 )
+ {
+ if ( content == null )
+ {
+ content = ( String[] ) added.clone();
+ }
+ else
+ {
+ String[] newContent = new String[content.length + added.length];
+ System.arraycopy( content, 0, newContent, 0, content.length );
+ System.arraycopy( added, 0, newContent, content.length, added.length );
+ }
+ }
+ }
+
+
+ public void addContent( String content )
+ {
+ if ( content != null )
+ {
+ addContent( AD.splitList( content ) );
+ }
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/DefaultMetaTypeProvider.java b/metatype/src/main/java/org/apache/felix/metatype/DefaultMetaTypeProvider.java
new file mode 100644
index 0000000..72caf0f
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/DefaultMetaTypeProvider.java
@@ -0,0 +1,197 @@
+/*
+ * 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.metatype;
+
+
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.felix.metatype.internal.LocalizedObjectClassDefinition;
+import org.apache.felix.metatype.internal.l10n.BundleResources;
+import org.apache.felix.metatype.internal.l10n.Resources;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.service.metatype.MetaTypeProvider;
+import org.osgi.service.metatype.MetaTypeService;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+
+/**
+ * The <code>DefaultMetaTypeProvider</code> class is an implementation of the
+ * <code>MetaTypeProvider</code> interface whichis configured for a given
+ * bundle using a {@link MetaData} object.
+ * <p>
+ * This class may be used by clients, e.g. <code>ManagedService</code> or
+ * <code>ManagedServiceFactory</code> implementations to easily also implement
+ * the <code>MetaTypeProvider</code> interface.
+ *
+ * @author fmeschbe
+ */
+public class DefaultMetaTypeProvider implements MetaTypeProvider
+{
+
+ private final Bundle bundle;
+
+ private String localePrefix;
+ private Map objectClassDefinitions;
+ private Map designates;
+
+ private Map locales;
+
+
+ public DefaultMetaTypeProvider( Bundle bundle, MetaData metadata )
+ {
+ this.bundle = bundle;
+
+ // copy from holder
+ if ( metadata.getObjectClassDefinitions() == null )
+ {
+ objectClassDefinitions = Collections.EMPTY_MAP;
+ }
+ else
+ {
+ Map copy = new HashMap( metadata.getObjectClassDefinitions() );
+ objectClassDefinitions = Collections.unmodifiableMap( copy );
+ }
+ if ( metadata.getDesignates() == null )
+ {
+ designates = Collections.EMPTY_MAP;
+ }
+ else
+ {
+ Map copy = new HashMap( metadata.getDesignates() );
+ designates = Collections.unmodifiableMap( copy );
+ }
+
+ localePrefix = metadata.getLocalePrefix();
+ if ( localePrefix == null )
+ {
+ localePrefix = ( String ) bundle.getHeaders().get( Constants.BUNDLE_LOCALIZATION );
+ if ( localePrefix == null )
+ {
+ localePrefix = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
+ }
+ }
+ else
+ {
+ localePrefix = MetaTypeService.METATYPE_DOCUMENTS_LOCATION + "/" + localePrefix;
+ }
+ }
+
+
+ /**
+ * Returns the <code>Bundle</code> to which this instance belongs.
+ */
+ public Bundle getBundle()
+ {
+ return bundle;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.metatype.MetaTypeProvider#getLocales()
+ */
+ public String[] getLocales()
+ {
+ if ( locales == null )
+ {
+ String path;
+ String pattern;
+ int lastSlash = localePrefix.lastIndexOf( '/' );
+ if ( lastSlash < 0 )
+ {
+ path = "/";
+ pattern = localePrefix;
+ }
+ else
+ {
+ path = localePrefix.substring( 0, lastSlash );
+ pattern = localePrefix.substring( lastSlash + 1 );
+ }
+
+ Enumeration entries = getBundle().findEntries( path, pattern + "*.properties", false );
+ locales = new TreeMap();
+ while ( entries.hasMoreElements() )
+ {
+ URL url = ( URL ) entries.nextElement();
+ String name = url.getPath();
+ name = name.substring( name.lastIndexOf( '/' ) + 1 + pattern.length(), name.length()
+ - ".properties".length() );
+ if ( name.startsWith( "_" ) )
+ {
+ name = name.substring( 1 );
+ }
+ locales.put( name, url );
+ }
+ }
+
+ // no locales found
+ if ( locales.isEmpty() )
+ {
+ return null;
+ }
+
+ return ( String[] ) locales.keySet().toArray( new String[locales.size()] );
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.metatype.MetaTypeProvider#getObjectClassDefinition(java.lang.String, java.lang.String)
+ */
+ public ObjectClassDefinition getObjectClassDefinition( String id, String locale )
+ {
+ Designate designate = getDesignate( id );
+ if ( designate == null || designate.getObject() == null )
+ {
+ return null;
+ }
+
+ String ocdRef = designate.getObject().getOcdRef();
+ OCD ocd = ( OCD ) objectClassDefinitions.get( ocdRef );
+ if ( ocd == null )
+ {
+ return null;
+ }
+
+ Resources resources = BundleResources.getResources( bundle, localePrefix, locale );
+ return new LocalizedObjectClassDefinition( bundle, ocd, resources );
+ }
+
+
+ public Designate getDesignate( String pid )
+ {
+ return ( Designate ) designates.get( pid );
+ }
+
+ protected Map getObjectClassDefinitions()
+ {
+ return objectClassDefinitions;
+ }
+
+
+ protected Map getDesignates()
+ {
+ return designates;
+ }
+
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/Designate.java b/metatype/src/main/java/org/apache/felix/metatype/Designate.java
new file mode 100644
index 0000000..997f998
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/Designate.java
@@ -0,0 +1,150 @@
+/*
+ * 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.metatype;
+
+
+/**
+ * The <code>Designate</code> class represents the <code>Designate</code>
+ * element of the meta type descriptor.
+ *
+ * @author fmeschbe
+ */
+public class Designate
+{
+
+ private String pid;
+
+ private String factoryPid;
+
+ private String bundleLocation;
+
+ private boolean optional;
+
+ private boolean merge;
+
+ private DesignateObject object;
+
+
+ /**
+ * @return the bundleLocation
+ */
+ public String getBundleLocation()
+ {
+ return bundleLocation;
+ }
+
+
+ /**
+ * @return the factoryPid
+ */
+ public String getFactoryPid()
+ {
+ return factoryPid;
+ }
+
+
+ /**
+ * @return the merge
+ */
+ public boolean isMerge()
+ {
+ return merge;
+ }
+
+
+ /**
+ * @return the optional
+ */
+ public boolean isOptional()
+ {
+ return optional;
+ }
+
+
+ /**
+ * @return the pid
+ */
+ public String getPid()
+ {
+ return pid;
+ }
+
+
+ /**
+ * @return the object
+ */
+ public DesignateObject getObject()
+ {
+ return object;
+ }
+
+
+ /**
+ * @param bundleLocation the bundleLocation to set
+ */
+ public void setBundleLocation( String bundleLocation )
+ {
+ this.bundleLocation = bundleLocation;
+ }
+
+
+ /**
+ * @param factoryPid the factoryPid to set
+ */
+ public void setFactoryPid( String factoryPid )
+ {
+ this.factoryPid = factoryPid;
+ }
+
+
+ /**
+ * @param merge the merge to set
+ */
+ public void setMerge( boolean merge )
+ {
+ this.merge = merge;
+ }
+
+
+ /**
+ * @param optional the optional to set
+ */
+ public void setOptional( boolean optional )
+ {
+ this.optional = optional;
+ }
+
+
+ /**
+ * @param pid the pid to set
+ */
+ public void setPid( String pid )
+ {
+ this.pid = pid;
+ }
+
+
+ /**
+ * @param object the object to set
+ */
+ public void setObject( DesignateObject object )
+ {
+ this.object = object;
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/DesignateObject.java b/metatype/src/main/java/org/apache/felix/metatype/DesignateObject.java
new file mode 100644
index 0000000..611aae6
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/DesignateObject.java
@@ -0,0 +1,68 @@
+/*
+ * 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.metatype;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * The <code>DesignateObject</code> class represents the <code>Object</code> element of
+ * the meta type descriptor.
+ *
+ * @author fmeschbe
+ */
+public class DesignateObject
+{
+
+ private String ocdRef;
+ private List attributes;
+
+
+ public String getOcdRef()
+ {
+ return ocdRef;
+ }
+
+
+ public void setOcdRef( String ocdRef )
+ {
+ this.ocdRef = ocdRef;
+ }
+
+
+ public List getAttributes()
+ {
+ return attributes;
+ }
+
+
+ public void addAttribute( Attribute attribute )
+ {
+ if ( attribute != null )
+ {
+ if ( attributes == null )
+ {
+ attributes = new ArrayList();
+ }
+ attributes.add( attribute );
+ }
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/MetaData.java b/metatype/src/main/java/org/apache/felix/metatype/MetaData.java
new file mode 100644
index 0000000..6b0fe3f
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/MetaData.java
@@ -0,0 +1,91 @@
+/*
+ * 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.metatype;
+
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+/**
+ * The <code>MetaData</code> class represents the <code>MetaData</code>
+ * element of the meta type descriptor.
+ *
+ * @author fmeschbe
+ */
+public class MetaData
+{
+
+ private String localePrefix;
+ private Map objectClassDefinitions;
+ private Map designates;
+
+
+ public String getLocalePrefix()
+ {
+ return localePrefix;
+ }
+
+
+ public void setLocalePrefix( String localePrefix )
+ {
+ this.localePrefix = localePrefix;
+ }
+
+
+ public Map getObjectClassDefinitions()
+ {
+ return objectClassDefinitions;
+ }
+
+
+ public void addObjectClassDefinition( OCD objectClassDefinition )
+ {
+ if ( objectClassDefinition != null )
+ {
+ if ( objectClassDefinitions == null )
+ {
+ objectClassDefinitions = new LinkedHashMap();
+ }
+
+ objectClassDefinitions.put( objectClassDefinition.getID(), objectClassDefinition );
+ }
+ }
+
+
+ public Map getDesignates()
+ {
+ return designates;
+ }
+
+
+ public void addDesignate( Designate designate )
+ {
+ if ( designate != null )
+ {
+ if ( designates == null )
+ {
+ designates = new HashMap();
+ }
+
+ designates.put( designate.getPid(), designate );
+ }
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/MetaDataReader.java b/metatype/src/main/java/org/apache/felix/metatype/MetaDataReader.java
new file mode 100644
index 0000000..d58609b
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/MetaDataReader.java
@@ -0,0 +1,579 @@
+/*
+ * 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.metatype;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.felix.metatype.internal.Activator;
+import org.kxml2.io.KXmlParser;
+import org.osgi.service.log.LogService;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+
+/**
+ * The <code>MetaDataReader</code> provides two methods to read meta type
+ * documents according to the MetaType schema (105.8 XML Schema). The
+ * {@link #parse(URL)} and {@link #parse(InputStream)} methods may be called
+ * multiple times to parse such documents.
+ * <p>
+ * While reading the XML document java objects are created to hold the data.
+ * These objects are created by factory methods. Users of this may extend this
+ * class by overwriting the the factory methods to create specialized versions.
+ * One notable use of this is the extension of the {@link AD} class to overwrite
+ * the {@link AD#validate(String)} method. In this case, the {@link #createAD()}
+ * method would be overwritten to return an instance of the extending class.
+ * <p>
+ * This class is not thread safe. Using instances of this class in multiple
+ * threads concurrently is not supported and will fail.
+ *
+ * @author fmeschbe
+ */
+public class MetaDataReader
+{
+
+ /** The XML parser used to read the XML documents */
+ private KXmlParser parser = new KXmlParser();
+
+
+ /**
+ * Parses the XML document provided by the <code>url</code>. The XML document
+ * must be at the beginning of the stream contents.
+ * <p>
+ * This method is almost identical to
+ * <code>return parse(url.openStream());</code> but also sets the string
+ * representation of the URL as a location helper for error messages.
+ *
+ * @param url The <code>URL</code> providing access to the XML document.
+ *
+ * @return A {@link MetaData} providing access to the
+ * raw contents of the XML document.
+ *
+ * @throws IOException If an I/O error occurrs accessing the stream.
+ * @throws XmlPullParserException If an error occurrs parsing the XML
+ * document.
+ */
+ public MetaData parse( URL url ) throws IOException, XmlPullParserException
+ {
+ InputStream ins = null;
+ try
+ {
+ ins = url.openStream();
+ parser.setProperty( "http://xmlpull.org/v1/doc/properties.html#location", url.toString() );
+ return parse( ins );
+ }
+ finally
+ {
+ if ( ins != null )
+ {
+ try
+ {
+ ins.close();
+ }
+ catch ( IOException ioe )
+ {
+ // ignore
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Parses the XML document in the given input stream.
+ * <p>
+ * This method starts reading at the current position of the input stream
+ * and returns immediately after completely reading a single meta type
+ * document. The stream is not closed by this method.
+ *
+ * @param ins The <code>InputStream</code> providing the XML document
+ *
+ * @return A {@link MetaData} providing access to the
+ * raw contents of the XML document.
+ *
+ * @throws IOException If an I/O error occurrs accessing the stream.
+ * @throws XmlPullParserException If an error occurrs parsing the XML
+ * document.
+ */
+ public MetaData parse( InputStream ins ) throws IOException, XmlPullParserException
+ {
+ // set the parser input, use null encoding to force detection with <?xml?>
+ parser.setInput( ins, null );
+
+ MetaData mti = null;
+
+ int eventType = parser.getEventType();
+ while ( eventType != XmlPullParser.END_DOCUMENT )
+ {
+ if ( eventType == XmlPullParser.START_TAG )
+ {
+ if ( "MetaData".equals( parser.getName() ) )
+ {
+ mti = readMetaData();
+ }
+ else
+ {
+ ignoreElement();
+ }
+ }
+ eventType = parser.next();
+ }
+
+ return mti;
+ }
+
+
+ private MetaData readMetaData() throws IOException, XmlPullParserException
+ {
+ MetaData mti = createMetaData();
+ mti.setLocalePrefix( getOptionalAttribute( "localization" ) );
+
+ int eventType = parser.next();
+ while ( eventType != XmlPullParser.END_DOCUMENT )
+ {
+ if ( eventType == XmlPullParser.START_TAG )
+ {
+ if ( "OCD".equals( parser.getName() ) )
+ {
+ mti.addObjectClassDefinition( readOCD() );
+ }
+ else if ( "Designate".equals( parser.getName() ) )
+ {
+ mti.addDesignate( readDesignate() );
+ }
+ else
+ {
+ ignoreElement();
+ }
+ }
+ else if ( eventType == XmlPullParser.END_TAG )
+ {
+ if ( "MetaData".equals( parser.getName() ) )
+ {
+ break;
+ }
+
+ throw unexpectedElement();
+ }
+ eventType = parser.next();
+ }
+
+ return mti;
+ }
+
+
+ private OCD readOCD() throws IOException, XmlPullParserException
+ {
+ OCD ocd = createOCD();
+ ocd.setId( getRequiredAttribute( "id" ) );
+ ocd.setName( getRequiredAttribute( "name" ) );
+ ocd.setDescription( getOptionalAttribute( "description" ) );
+
+ int eventType = parser.next();
+ while ( eventType != XmlPullParser.END_DOCUMENT )
+ {
+ if ( eventType == XmlPullParser.START_TAG )
+ {
+ if ( "AD".equals( parser.getName() ) )
+ {
+ ocd.addAttributeDefinition( readAD() );
+ }
+ else if ( "Icon".equals( parser.getName() ) )
+ {
+ String res = getRequiredAttribute( "resource" );
+ String sizeString = getRequiredAttribute( "size" );
+ try
+ {
+ Integer size = Integer.decode( sizeString );
+ ocd.addIcon( size, res );
+ }
+ catch ( NumberFormatException nfe )
+ {
+ Activator.log( LogService.LOG_DEBUG, "readOCD: Icon size '" + sizeString
+ + "' is not a valid number" );
+ }
+ }
+ else
+ {
+ ignoreElement();
+ }
+ }
+ else if ( eventType == XmlPullParser.END_TAG )
+ {
+ if ( "OCD".equals( parser.getName() ) )
+ {
+ break;
+ }
+ else if ( !"Icon".equals( parser.getName() ) )
+ {
+ throw unexpectedElement();
+ }
+ }
+ eventType = parser.next();
+ }
+
+ return ocd;
+ }
+
+
+ private Designate readDesignate() throws IOException, XmlPullParserException
+ {
+ Designate designate = createDesignate();
+ designate.setPid( getRequiredAttribute( "pid" ) );
+ designate.setFactoryPid( getOptionalAttribute( "factoryPid" ) );
+ designate.setBundleLocation( getOptionalAttribute( "bundle" ) );
+ designate.setOptional( getOptionalAttribute( "optional", false ) );
+ designate.setMerge( getOptionalAttribute( "merge", false ) );
+
+ int eventType = parser.next();
+ while ( eventType != XmlPullParser.END_DOCUMENT )
+ {
+ if ( eventType == XmlPullParser.START_TAG )
+ {
+ if ( "Object".equals( parser.getName() ) )
+ {
+ designate.setObject( readObject() );
+ }
+ else
+ {
+ ignoreElement();
+ }
+ }
+ else if ( eventType == XmlPullParser.END_TAG )
+ {
+ if ( "Designate".equals( parser.getName() ) )
+ {
+ break;
+ }
+
+ throw unexpectedElement();
+ }
+ eventType = parser.next();
+ }
+
+ return designate;
+ }
+
+
+ private AD readAD() throws IOException, XmlPullParserException
+ {
+ AD ad = createAD();
+ ad.setID( getRequiredAttribute( "id" ) );
+ ad.setName( getRequiredAttribute( "name" ) );
+ ad.setDescription( getOptionalAttribute( "description" ) );
+ ad.setType( getRequiredAttribute( "type" ) );
+ ad.setCardinality( getOptionalAttribute( "cardinality", 0 ) );
+ ad.setMin( getOptionalAttribute( "min" ) );
+ ad.setMax( getOptionalAttribute( "min" ) );
+ ad.setDefaultValue( getOptionalAttribute( "default" ) );
+ ad.setRequired( getOptionalAttribute( "required", true ) );
+
+ Map options = new LinkedHashMap();
+ int eventType = parser.next();
+ while ( eventType != XmlPullParser.END_DOCUMENT )
+ {
+ if ( eventType == XmlPullParser.START_TAG )
+ {
+ if ( "Option".equals( parser.getName() ) )
+ {
+ String value = getRequiredAttribute( "value" );
+ String label = getRequiredAttribute( "label" );
+ options.put( value, label );
+ }
+ else
+ {
+ ignoreElement();
+ }
+ }
+ else if ( eventType == XmlPullParser.END_TAG )
+ {
+ if ( "AD".equals( parser.getName() ) )
+ {
+ break;
+ }
+ else if ( !"Option".equals( parser.getName() ) )
+ {
+ throw unexpectedElement();
+ }
+ }
+ eventType = parser.next();
+ }
+
+ ad.setOptions( options );
+
+ return ad;
+ }
+
+
+ private DesignateObject readObject() throws IOException, XmlPullParserException
+ {
+ DesignateObject oh = createDesignateObject();
+ oh.setOcdRef( getRequiredAttribute( "ocdref" ) );
+
+ int eventType = parser.next();
+ while ( eventType != XmlPullParser.END_DOCUMENT )
+ {
+ if ( eventType == XmlPullParser.START_TAG )
+ {
+ if ( "Attribute".equals( parser.getName() ) )
+ {
+ oh.addAttribute( readAttribute() );
+ }
+ else
+ {
+ ignoreElement();
+ }
+ }
+ else if ( eventType == XmlPullParser.END_TAG )
+ {
+ if ( "Object".equals( parser.getName() ) )
+ {
+ break;
+ }
+ throw unexpectedElement();
+ }
+ eventType = parser.next();
+ }
+
+ return oh;
+ }
+
+
+ private Attribute readAttribute() throws IOException, XmlPullParserException
+ {
+ Attribute ah = createAttribute();
+ ah.setAdRef( getRequiredAttribute( "adref" ) );
+ ah.addContent( getOptionalAttribute( "content" ) );
+
+ int eventType = parser.next();
+ while ( eventType != XmlPullParser.END_DOCUMENT )
+ {
+ if ( eventType == XmlPullParser.START_TAG )
+ {
+ if ( "Value".equals( parser.getName() ) )
+ {
+ ah.addContent( parser.nextText() );
+ eventType = parser.getEventType();
+ continue;
+ }
+ else
+ {
+ ignoreElement();
+ }
+ }
+ else if ( eventType == XmlPullParser.END_TAG )
+ {
+ if ( "Attribute".equals( parser.getName() ) )
+ {
+ break;
+ }
+ else if ( !"Value".equals( parser.getName() ) )
+ {
+ throw unexpectedElement();
+ }
+ }
+ eventType = parser.next();
+ }
+
+ return ah;
+ }
+
+
+ //---------- Attribute access helper --------------------------------------
+
+ private String getRequiredAttribute( String attrName ) throws XmlPullParserException
+ {
+ String attrVal = parser.getAttributeValue( null, attrName );
+ if ( attrVal != null )
+ {
+ return attrVal;
+ }
+
+ // fail if value is missing
+ throw missingAttribute( attrName );
+ }
+
+
+ private String getOptionalAttribute( String attrName )
+ {
+ return getOptionalAttribute( attrName, ( String ) null );
+ }
+
+
+ private String getOptionalAttribute( String attrName, String defaultValue )
+ {
+ String attrVal = parser.getAttributeValue( null, attrName );
+ return ( attrVal != null ) ? attrVal : defaultValue;
+ }
+
+
+ private boolean getOptionalAttribute( String attrName, boolean defaultValue )
+ {
+ String attrVal = parser.getAttributeValue( null, attrName );
+ return ( attrVal != null ) ? "true".equalsIgnoreCase( attrVal ) : defaultValue;
+ }
+
+
+ private int getOptionalAttribute( String attrName, int defaultValue )
+ {
+ String attrVal = parser.getAttributeValue( null, attrName );
+ if ( attrVal != null && attrVal.length() > 0 )
+ {
+ try
+ {
+ return Integer.decode( attrVal ).intValue();
+ }
+ catch ( NumberFormatException nfe )
+ {
+ Activator.log( LogService.LOG_DEBUG, "getOptionalAttribute: Value '" + attrVal + "' of attribute "
+ + attrName + " is not a valid number. Using default value " + defaultValue );
+ }
+ }
+
+ // fallback to default
+ return defaultValue;
+ }
+
+
+ //---------- Error Handling support ---------------------------------------
+
+ private void ignoreElement() throws IOException, XmlPullParserException
+ {
+ String ignoredElement = parser.getName();
+
+ int depth = 0; // enable nested ignored elements
+ int eventType = parser.next();
+ while ( eventType != XmlPullParser.END_DOCUMENT )
+ {
+ if ( eventType == XmlPullParser.START_TAG )
+ {
+ if ( ignoredElement.equals( parser.getName() ) )
+ {
+ depth++;
+ }
+ }
+ else if ( eventType == XmlPullParser.END_TAG )
+ {
+ if ( ignoredElement.equals( parser.getName() ) )
+ {
+ if ( depth <= 0 )
+ {
+ return;
+ }
+
+ depth--;
+ }
+ }
+ eventType = parser.next();
+ }
+ }
+
+
+ private XmlPullParserException missingAttribute( String attrName )
+ {
+ String message = "Missing Attribute " + attrName + " in element " + parser.getName();
+ return new XmlPullParserException( message, parser, null );
+ }
+
+
+ private XmlPullParserException unexpectedElement()
+ {
+ String message = "Illegal Element " + parser.getName();
+ return new XmlPullParserException( message, parser, null );
+ }
+
+
+ //---------- Factory methods ----------------------------------------------
+
+ /**
+ * Creates a new {@link MetaData} object to hold the contents of the
+ * <code>MetaData</code> element.
+ * <p>
+ * This method may be overwritten to return a customized extension.
+ */
+ protected MetaData createMetaData()
+ {
+ return new MetaData();
+ }
+
+
+ /**
+ * Creates a new {@link OCD} object to hold the contents of the
+ * <code>OCD</code> element.
+ * <p>
+ * This method may be overwritten to return a customized extension.
+ */
+ protected OCD createOCD()
+ {
+ return new OCD();
+ }
+
+
+ /**
+ * Creates a new {@link AD} object to hold the contents of the
+ * <code>AD</code> element.
+ * <p>
+ * This method may be overwritten to return a customized extension.
+ */
+ protected AD createAD()
+ {
+ return new AD();
+ }
+
+
+ /**
+ * Creates a new {@link DesignateObject} object to hold the contents of the
+ * <code>Object</code> element.
+ * <p>
+ * This method may be overwritten to return a customized extension.
+ */
+ protected DesignateObject createDesignateObject()
+ {
+ return new DesignateObject();
+ }
+
+
+ /**
+ * Creates a new {@link Attribute} object to hold the contents of the
+ * <code>Attribute</code> element.
+ * <p>
+ * This method may be overwritten to return a customized extension.
+ */
+ protected Attribute createAttribute()
+ {
+ return new Attribute();
+ }
+
+
+ /**
+ * Creates a new {@link Designate} object to hold the contents of the
+ * <code>Designate</code> element.
+ * <p>
+ * This method may be overwritten to return a customized extension.
+ */
+ protected Designate createDesignate()
+ {
+ return new Designate();
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/OCD.java b/metatype/src/main/java/org/apache/felix/metatype/OCD.java
new file mode 100644
index 0000000..2ace642
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/OCD.java
@@ -0,0 +1,123 @@
+/*
+ * 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.metatype;
+
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+/**
+ * The <code>OCD</code> class represents the <code>OCD</code> element of the
+ * meta type descriptor.
+ *
+ * @author fmeschbe
+ */
+public class OCD
+{
+
+ private String id;
+ private String name;
+ private String description;
+ private Map attributes;
+ private Map icons;
+
+
+ public String getID()
+ {
+ return id;
+ }
+
+
+ public void setId( String id )
+ {
+ this.id = id;
+ }
+
+
+ public String getName()
+ {
+ return name;
+ }
+
+
+ public void setName( String name )
+ {
+ this.name = name;
+ }
+
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+
+ public void setDescription( String description )
+ {
+ this.description = description;
+ }
+
+
+ public Map getIcons()
+ {
+ return icons;
+ }
+
+
+ /**
+ *
+ * @param size
+ * @param icon The icon, either an URL or a string designating a resource
+ * which may be a localized string
+ */
+ public void addIcon( Integer size, String icon )
+ {
+ if ( icon != null )
+ {
+ if ( icons == null )
+ {
+ icons = new HashMap();
+ }
+
+ icons.put( size, icon );
+ }
+ }
+
+
+ public Map getAttributeDefinitions()
+ {
+ return attributes;
+ }
+
+
+ public void addAttributeDefinition( AD attribute )
+ {
+ if ( attribute != null )
+ {
+ if ( attributes == null )
+ {
+ attributes = new LinkedHashMap();
+ }
+
+ attributes.put( attribute.getID(), attribute );
+ }
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/internal/Activator.java b/metatype/src/main/java/org/apache/felix/metatype/internal/Activator.java
new file mode 100644
index 0000000..291fa2b
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/internal/Activator.java
@@ -0,0 +1,247 @@
+/*
+ * 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.metatype.internal;
+
+
+import java.io.PrintStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.metatype.internal.l10n.BundleResources;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+import org.osgi.service.metatype.MetaTypeService;
+import org.osgi.util.tracker.ServiceTracker;
+
+
+/**
+ * The <code>Activator</code> class is the <code>BundleActivator</code> of
+ * this bundle and provides abstract logging functionality: If a
+ * <code>LogService</code> is available, that service is used, otherwise
+ * logging goes to standard output or standard error (in case of level ERROR
+ * messages).
+ *
+ * @author fmeschbe
+ */
+public class Activator implements BundleActivator
+{
+
+ /** The name of the log service. */
+ private static final String NAME_LOG_SERVICE = LogService.class.getName();
+
+ /**
+ * A <code>SimpleDateFormat</code> object to format the a log message time
+ * stamp in case of logging to standard output/error (value of format
+ * pattern is "dd.MM.yyyy HH:mm:ss").
+ */
+ private static final SimpleDateFormat FORMAT = new SimpleDateFormat( "dd.MM.yyyy HH:mm:ss" );
+
+ /**
+ * The (singleton) instance of this activator. Used by the log methods to
+ * access the {@link #logService} field.
+ */
+ private static Activator INSTANCE;
+
+ /**
+ * The <code>LogService</code> used to log messages. If a log service is
+ * not available in the framework, this field is <code>null</code>.
+ *
+ * @see #start(BundleContext)
+ * @see #serviceChanged(ServiceEvent)
+ */
+ private ServiceTracker logService;
+
+ /*
+ * Set the static INSTANCE field to this new instance
+ */
+ {
+ INSTANCE = this;
+ }
+
+
+ /**
+ * Starts this bundle doing the following:
+ * <ol>
+ * <li>Register as listener for service events concerning the
+ * <code>LogService</code> (see {@link #serviceChanged(ServiceEvent)}
+ * <li>Try to get the <code>LogService</code>
+ * <li>Registers the <code>MetaTypeService</code> implementation provided
+ * by this bundle.
+ * </ol>
+ *
+ * @param context The <code>BundleContext</code> of this activator's bundle
+ */
+ public void start( BundleContext context )
+ {
+ // register for log service events
+ logService = new ServiceTracker( context, NAME_LOG_SERVICE, null );
+ logService.open();
+
+ // register the MetaTypeService now, that we are ready
+ Dictionary props = new Hashtable();
+ props.put( Constants.SERVICE_PID, "org.apache.felix.metatype.MetaTypeService" );
+ props.put( Constants.SERVICE_DESCRIPTION, "MetaTypeService Specification 1.1 Implementation" );
+ props.put( Constants.SERVICE_VENDOR, "Apache Software Foundation" );
+ MetaTypeService metaTypeService = new MetaTypeServiceImpl( context );
+ context.registerService( MetaTypeService.class.getName(), metaTypeService, props );
+ }
+
+
+ /**
+ * Stops this bundle by just unregistering as a service listener.
+ * <p>
+ * The framework will take care of ungetting the <code>LogService</code> and
+ * unregistering the <code>MetaTypeService</code> registered by the
+ * {@link #start(BundleContext)} method.
+ *
+ * @param context The <code>BundleContext</code> of this activator's bundle
+ */
+ public void stop( BundleContext context )
+ {
+ logService.close();
+
+ // make sure the static BundleResources cache does not block the class laoder
+ BundleResources.clearResourcesCache();
+ }
+
+ //---------- Logging Support ----------------------------------------------
+ // log to stdout or use LogService
+
+ public static void log( int level, String message )
+ {
+ LogService log = ( LogService ) INSTANCE.logService.getService();
+ if ( log == null )
+ {
+ _log( null, level, message, null );
+ }
+ else
+ {
+ log.log( level, message );
+ }
+ }
+
+
+ public static void log( int level, String message, Throwable exception )
+ {
+ LogService log = ( LogService ) INSTANCE.logService.getService();
+ if ( log == null )
+ {
+ _log( null, level, message, exception );
+ }
+ else
+ {
+ log.log( level, message, exception );
+ }
+ }
+
+
+ public static void log( ServiceReference sr, int level, String message )
+ {
+ LogService log = ( LogService ) INSTANCE.logService.getService();
+ if ( log == null )
+ {
+ _log( sr, level, message, null );
+ }
+ else
+ {
+ log.log( sr, level, message );
+ }
+ }
+
+
+ public static void log( ServiceReference sr, int level, String message, Throwable exception )
+ {
+ LogService log = ( LogService ) INSTANCE.logService.getService();
+ if ( log == null )
+ {
+ _log( sr, level, message, exception );
+ }
+ else
+ {
+ log.log( sr, level, message, exception );
+ }
+ }
+
+
+ //---------- Helper Methods -----------------------------------------------
+
+ private static void _log( ServiceReference sr, int level, String message, Throwable exception )
+ {
+ String time = getTimeStamp();
+
+ StringBuffer buf = new StringBuffer( time );
+ buf.append( ' ' ).append( toLevelString( level ) ).append( ' ' );
+ buf.append( message );
+
+ if ( sr != null )
+ {
+ String name = ( String ) sr.getProperty( Constants.SERVICE_PID );
+ if ( name == null )
+ {
+ name = ( ( String[] ) sr.getProperty( Constants.OBJECTCLASS ) )[0];
+ }
+ buf.append( " (" ).append( name ).append( ", service.id=" ).append( sr.getProperty( Constants.SERVICE_ID ) )
+ .append( ')' );
+ }
+
+ PrintStream dst = ( level == LogService.LOG_ERROR ) ? System.err : System.out;
+ dst.println( buf );
+
+ if ( exception != null )
+ {
+ buf = new StringBuffer( time );
+ buf.append( ' ' ).append( toLevelString( level ) ).append( ' ' );
+ dst.print( buf );
+ exception.printStackTrace( dst );
+ }
+ }
+
+
+ private static String getTimeStamp()
+ {
+ synchronized ( FORMAT )
+ {
+ return FORMAT.format( new Date() );
+ }
+ }
+
+
+ private static String toLevelString( int level )
+ {
+ switch ( level )
+ {
+ case LogService.LOG_DEBUG:
+ return "*DEBUG*";
+ case LogService.LOG_INFO:
+ return "*INFO *";
+ case LogService.LOG_WARNING:
+ return "*WARN *";
+ case LogService.LOG_ERROR:
+ return "*ERROR*";
+ default:
+ return "*" + level + "*";
+ }
+
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/internal/LocalizedAttributeDefinition.java b/metatype/src/main/java/org/apache/felix/metatype/internal/LocalizedAttributeDefinition.java
new file mode 100644
index 0000000..b5ce0d8
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/internal/LocalizedAttributeDefinition.java
@@ -0,0 +1,151 @@
+/*
+ * 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.metatype.internal;
+
+
+import org.apache.felix.metatype.AD;
+import org.apache.felix.metatype.internal.l10n.Resources;
+import org.osgi.service.metatype.AttributeDefinition;
+
+
+/**
+ * The <code>LocalizedAttributeDefinition</code> class is the implementation
+ * of the <code>AttributeDefinition</code> interface. This class delegates
+ * calls to the underlying {@link AD} localizing the results of the following
+ * methods: {@link #getName()}, {@link #getDescription()},
+ * {@link #getOptionLabels()}, and {@link #validate(String)}.
+ *
+ * @author fmeschbe
+ */
+class LocalizedAttributeDefinition extends LocalizedBase implements AttributeDefinition
+{
+
+ private final AD ad;
+
+
+ /**
+ * Creates and instance of this localizing facade.
+ *
+ * @param ad The {@link AD} to which calls are delegated.
+ * @param resources The {@link Resources} used to localize return values of
+ * localizable methods.
+ */
+ LocalizedAttributeDefinition( AD ad, Resources resources )
+ {
+ super( resources );
+ this.ad = ad;
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.AttributeDefinition#getCardinality()
+ */
+ public int getCardinality()
+ {
+ return ad.getCardinality();
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.AttributeDefinition#getDefaultValue()
+ */
+ public String[] getDefaultValue()
+ {
+ return ad.getDefaultValue();
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.AttributeDefinition#getDescription()
+ */
+ public String getDescription()
+ {
+ return localize( ad.getDescription() );
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.AttributeDefinition#getID()
+ */
+ public String getID()
+ {
+ return ad.getID();
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.AttributeDefinition#getName()
+ */
+ public String getName()
+ {
+ return localize( ad.getName() );
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.AttributeDefinition#getOptionLabels()
+ */
+ public String[] getOptionLabels()
+ {
+ return localize( ad.getOptionLabels() );
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.AttributeDefinition#getOptionValues()
+ */
+ public String[] getOptionValues()
+ {
+ return ad.getOptionValues();
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.AttributeDefinition#getType()
+ */
+ public int getType()
+ {
+ return ad.getType();
+ }
+
+
+ /**
+ * @param value
+ * @return
+ * @see org.osgi.service.metatype.AttributeDefinition#validate(java.lang.String)
+ */
+ public String validate( String value )
+ {
+ String message = ad.validate( value );
+ if ( message == null || message.length() == 0 )
+ {
+ return message;
+ }
+
+ return localize( message );
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/internal/LocalizedBase.java b/metatype/src/main/java/org/apache/felix/metatype/internal/LocalizedBase.java
new file mode 100644
index 0000000..ab5de63
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/internal/LocalizedBase.java
@@ -0,0 +1,166 @@
+/*
+ * 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.metatype.internal;
+
+
+import org.apache.felix.metatype.internal.l10n.Resources;
+import org.osgi.service.log.LogService;
+
+
+/**
+ * The <code>LocalizedBase</code> class provides methods to localize strings
+ * and string arrays on demand using a <code>ResourceBundle</code> specified
+ * at construction time.
+ *
+ * @author fmeschbe
+ */
+abstract class LocalizedBase
+{
+
+ /**
+ * The {@link Resources} used to localize strings.
+ *
+ * @see #localize(String)
+ * @see #localize(String[])
+ */
+ private final Resources resources;
+
+
+ /**
+ * Sets up this class using the given <code>ResourceBundle</code>.
+ *
+ * @param resources The {@link Resources} used to localize return values of
+ * localizable methods.
+ *
+ * @throws NullPointerException If <code>resources</code> is
+ * <code>null</code>.
+ */
+ protected LocalizedBase( Resources resources )
+ {
+ if ( resources == null )
+ {
+ throw new NullPointerException( "resources" );
+ }
+ this.resources = resources;
+ }
+
+
+ /**
+ * Returns the {@link Resources} assigned to this instance.
+ */
+ protected Resources getResources()
+ {
+ return resources;
+ }
+
+
+ /**
+ * Calls {@link #localize(String)} for each string in the array and returns
+ * an array of the resulting localized strings. If <code>strings</code> is
+ * <code>null</code> <code>null</code> is returned.
+ *
+ * @param strings An array of non-<code>null</code> strings to localize.
+ *
+ * @return <code>null</code> if <code>strings</code> is <code>null</code> or
+ * an array of the same size as the <code>strings</code> array
+ * containing localized strings.
+ */
+ protected String[] localize( String[] strings )
+ {
+ if ( strings == null )
+ {
+ return null;
+ }
+
+ String[] localized = new String[strings.length];
+ for ( int i = 0; i < strings.length; i++ )
+ {
+ localized[i] = localize( strings[i] );
+ }
+ return localized;
+ }
+
+
+ /**
+ * Localizes the string using the
+ * {@link #getResourceBundle() ResourceBundle} set on this instance if
+ * string starts with the percent character (<code>%</code>). If the
+ * string is <code>null</code>, does not start with a percent character
+ * or the resource whose key is the string without the leading the percent
+ * character is not found the string is returned without the leading percent
+ * character.
+ * <p>
+ * Examples of different localizations:
+ * <p>
+ * <table border="0" cellspacing="0" cellpadding="3">
+ * <tr bgcolor="#ccccff">
+ * <th><code>string</code></th>
+ * <th>Key</th>
+ * <th>Resource</th>
+ * <th>Result</th>
+ * </tr>
+ * <tr>
+ * <td><code>null</code></td>
+ * <td>-</td>
+ * <td>-</td>
+ * <td><code>null</code></td>
+ * </tr>
+ * <tr bgcolor="#eeeeff">
+ * <td>sample</td>
+ * <td>-</td>
+ * <td>-</td>
+ * <td>sample</td>
+ * </tr>
+ * <tr>
+ * <td><b>%</b>sample</td>
+ * <td>sample</td>
+ * <td>-</td>
+ * <td>sample</td>
+ * </tr>
+ * <tr bgcolor="#eeeeff">
+ * <td><b>%</b>sample</td>
+ * <td>sample</td>
+ * <td>resource</td>
+ * <td>resource</td>
+ * </tr>
+ * </table>
+ *
+ * @param string The string to localize
+ * @return The localized string
+ */
+ protected String localize( String string )
+ {
+ if ( string != null && string.startsWith( "%" ) )
+ {
+ string = string.substring( 1 );
+ try
+ {
+ return getResources().getResource( string );
+ }
+ catch ( Exception e )
+ {
+ // ClassCastException, MissingResourceException
+ Activator.log( LogService.LOG_DEBUG, "localize: Failed getting resource '" + string + "'", e );
+ }
+ }
+
+ // just return the string unmodified
+ return string;
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/internal/LocalizedObjectClassDefinition.java b/metatype/src/main/java/org/apache/felix/metatype/internal/LocalizedObjectClassDefinition.java
new file mode 100644
index 0000000..3523761
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/internal/LocalizedObjectClassDefinition.java
@@ -0,0 +1,260 @@
+/*
+ * 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.metatype.internal;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import org.apache.felix.metatype.AD;
+import org.apache.felix.metatype.OCD;
+import org.apache.felix.metatype.internal.l10n.Resources;
+import org.osgi.framework.Bundle;
+import org.osgi.service.metatype.AttributeDefinition;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+
+/**
+ * The <code>LocalizedObjectClassDefinition</code> class is the implementation
+ * of the <code>ObjectClassDefinition</code> interface. This class delegates
+ * calls to the underlying {@link OCD} localizing the results of the following
+ * methods: {@link #getName()}, {@link #getDescription()}, and
+ * {@link #getIcon(int)}.
+ *
+ * @author fmeschbe
+ */
+public class LocalizedObjectClassDefinition extends LocalizedBase implements ObjectClassDefinition
+{
+
+ private Bundle bundle;
+
+ private OCD ocd;
+
+
+ /**
+ * Creates and instance of this localizing facade.
+ *
+ * @param bundle The <code>Bundle</code> providing this object class
+ * definition.
+ * @param ocd The {@link OCD} to which calls are delegated.
+ * @param resources The {@link Resources} used to localize return values of
+ * localizable methods.
+ */
+ public LocalizedObjectClassDefinition( Bundle bundle, OCD ocd, Resources resources )
+ {
+ super( resources );
+ this.bundle = bundle;
+ this.ocd = ocd;
+ }
+
+
+ /**
+ * @param filter
+ * @return
+ * @see org.osgi.service.metatype.ObjectClassDefinition#getAttributeDefinitions(int)
+ */
+ public AttributeDefinition[] getAttributeDefinitions( int filter )
+ {
+ if ( ocd.getAttributeDefinitions() == null )
+ {
+ return null;
+ }
+
+ Iterator adhIter = ocd.getAttributeDefinitions().values().iterator();
+ if ( filter == ObjectClassDefinition.OPTIONAL || filter == ObjectClassDefinition.REQUIRED )
+ {
+ boolean required = ( filter == ObjectClassDefinition.REQUIRED );
+ adhIter = new RequiredFilterIterator( adhIter, required );
+ }
+ else if ( filter != ObjectClassDefinition.ALL )
+ {
+ return null;
+ }
+
+ if ( !adhIter.hasNext() )
+ {
+ return null;
+ }
+
+ List result = new ArrayList();
+ while ( adhIter.hasNext() )
+ {
+ result.add( new LocalizedAttributeDefinition( ( AD ) adhIter.next(), getResources() ) );
+ }
+
+ return ( AttributeDefinition[] ) result.toArray( new AttributeDefinition[result.size()] );
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.ObjectClassDefinition#getDescription()
+ */
+ public String getDescription()
+ {
+ return localize( ocd.getDescription() );
+ }
+
+
+ /**
+ * @param size
+ * @return
+ * @throws IOException
+ * @see org.osgi.service.metatype.ObjectClassDefinition#getIcon(int)
+ */
+ public InputStream getIcon( int size ) throws IOException
+ {
+ // nothing if no icons are defined
+ Map icons = ocd.getIcons();
+ if ( icons == null )
+ {
+ return null;
+ }
+
+ // get exact size
+ String iconPath = ( String ) icons.get( new Integer( size ) );
+ if ( iconPath == null )
+ {
+ // approximate size: largest icon smaller than requested
+ Integer selected = new Integer( Integer.MIN_VALUE );
+ for ( Iterator ei = icons.keySet().iterator(); ei.hasNext(); )
+ {
+ Map.Entry entry = ( Map.Entry ) ei.next();
+ Integer keySize = ( Integer ) entry.getKey();
+ if ( keySize.intValue() <= size && selected.compareTo( keySize ) < 0 )
+ {
+ selected = keySize;
+ }
+ }
+ // get the raw path, fail if no path can be found
+ iconPath = ( String ) icons.get( selected );
+ }
+
+ // fail if no icon could be found
+ if ( iconPath == null )
+ {
+ return null;
+ }
+
+ // localize the path
+ iconPath = localize( iconPath );
+
+ // try to resolve the path in the bundle
+ URL url = bundle.getEntry( iconPath );
+ if ( url == null )
+ {
+ return null;
+ }
+
+ // open the stream on the URL - this may throw an IOException
+ return url.openStream();
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.ObjectClassDefinition#getID()
+ */
+ public String getID()
+ {
+ return ocd.getID();
+ }
+
+
+ /**
+ * @return
+ * @see org.osgi.service.metatype.ObjectClassDefinition#getName()
+ */
+ public String getName()
+ {
+ return localize( ocd.getName() );
+ }
+
+ private static class RequiredFilterIterator implements Iterator
+ {
+
+ private final Iterator base;
+
+ private final boolean required;
+
+ private AD next;
+
+
+ private RequiredFilterIterator( Iterator base, boolean required )
+ {
+ this.base = base;
+ this.required = required;
+ this.next = seek();
+ }
+
+
+ public boolean hasNext()
+ {
+ return next != null;
+ }
+
+
+ public Object next()
+ {
+ if ( !hasNext() )
+ {
+ throw new NoSuchElementException();
+ }
+
+ AD toReturn = next;
+ next = seek();
+ return toReturn;
+ }
+
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException( "remove" );
+ }
+
+
+ private AD seek()
+ {
+ if ( base.hasNext() )
+ {
+ AD next;
+ do
+ {
+ next = ( AD ) base.next();
+ }
+ while ( next.isRequired() != required && base.hasNext() );
+
+ if ( next.isRequired() == required )
+ {
+ return next;
+ }
+ }
+
+ // nothing found any more
+ return null;
+ }
+
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/internal/MetaTypeInformationImpl.java b/metatype/src/main/java/org/apache/felix/metatype/internal/MetaTypeInformationImpl.java
new file mode 100644
index 0000000..3bf1ad1
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/internal/MetaTypeInformationImpl.java
@@ -0,0 +1,215 @@
+/*
+ * 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.metatype.internal;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.felix.metatype.DefaultMetaTypeProvider;
+import org.apache.felix.metatype.Designate;
+import org.apache.felix.metatype.DesignateObject;
+import org.apache.felix.metatype.MetaData;
+import org.apache.felix.metatype.OCD;
+import org.osgi.framework.Bundle;
+import org.osgi.service.metatype.MetaTypeInformation;
+import org.osgi.service.metatype.MetaTypeProvider;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+/**
+ * The <code>MetaTypeInformationImpl</code> class implements the
+ * <code>MetaTypeInformation</code> interface returned from the
+ * <code>MetaTypeService</code>.
+ *
+ * @author fmeschbe
+ */
+public class MetaTypeInformationImpl implements MetaTypeInformation {
+
+ // also defined in org.osgi.service.cm.ConfigurationAdmin, but copied
+ // here to not create a synthetic dependency
+ public static final String SERVICE_FACTORYPID = "service.factoryPid";
+
+ private final Bundle bundle;
+
+ private Set pids;
+
+ private Set factoryPids;
+
+ private Set locales;
+
+ private Map metaTypeProviders;
+
+ protected MetaTypeInformationImpl(Bundle bundle) {
+ this.bundle = bundle;
+ this.pids = new TreeSet();
+ this.factoryPids = new TreeSet();
+ this.metaTypeProviders = new HashMap();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.osgi.service.metatype.MetaTypeInformation#getBundle()
+ */
+ public Bundle getBundle() {
+ return bundle;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.osgi.service.metatype.MetaTypeInformation#getFactoryPids()
+ */
+ public String[] getFactoryPids() {
+ return (String[]) factoryPids.toArray(new String[factoryPids.size()]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.osgi.service.metatype.MetaTypeInformation#getPids()
+ */
+ public String[] getPids() {
+ return (String[]) pids.toArray(new String[pids.size()]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.osgi.service.metatype.MetaTypeProvider#getLocales()
+ */
+ public String[] getLocales() {
+ if (locales == null) {
+ synchronized (this) {
+ Set newLocales = new TreeSet();
+ for (Iterator mi = metaTypeProviders.values().iterator(); mi.hasNext();) {
+ MetaTypeProvider mtp = (MetaTypeProvider) mi.next();
+ addValues(newLocales, mtp.getLocales());
+ }
+ locales = newLocales;
+ }
+ }
+
+ return (String[]) locales.toArray(new String[locales.size()]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.osgi.service.metatype.MetaTypeProvider#getObjectClassDefinition(java.lang.String,
+ * java.lang.String)
+ */
+ public ObjectClassDefinition getObjectClassDefinition(String id,
+ String locale) {
+ MetaTypeProvider mtp = (MetaTypeProvider) metaTypeProviders.get(id);
+ return (mtp != null) ? mtp.getObjectClassDefinition(id, locale) : null;
+ }
+
+ // ---------- internal support for metadata -------------------------------
+
+ Designate getDesignate( String pid )
+ {
+ Object mto = metaTypeProviders.get( pid );
+ if ( mto instanceof DefaultMetaTypeProvider )
+ {
+ return ( ( DefaultMetaTypeProvider ) mto ).getDesignate( pid );
+ }
+
+ return null;
+ }
+
+ // ---------- setters to fill the values -----------------------------------
+
+ protected void addMetaData(MetaData md) {
+ if (md.getDesignates() != null) {
+ // meta type provide to register by PID
+ DefaultMetaTypeProvider dmtp = new DefaultMetaTypeProvider(bundle, md);
+
+ Iterator designates = md.getDesignates().values().iterator();
+ while (designates.hasNext()) {
+ Designate designate = (Designate) designates.next();
+
+ // get the OCD reference, ignore the designate if none
+ DesignateObject object = designate.getObject();
+ String ocdRef = (object == null) ? null : object.getOcdRef();
+ if (ocdRef == null) {
+ continue;
+ }
+
+ // get ocd for the reference, ignore designate if none
+ OCD ocd = (OCD) md.getObjectClassDefinitions().get(ocdRef);
+ if (ocd == null) {
+ continue;
+ }
+
+ // gather pids and factory pids
+ pids.add(designate.getPid());
+ if (designate.getFactoryPid() != null) {
+ factoryPids.add( designate.getFactoryPid() );
+ }
+
+ // register a metatype provider for the pid
+ addMetaTypeProvider(designate.getPid(), dmtp);
+ }
+ }
+ }
+
+ protected void addPids(String[] pids) {
+ addValues(this.pids, pids);
+ }
+
+ protected void removePid(String pid) {
+ this.pids.remove(pid);
+ }
+
+ protected void addFactoryPids(String[] factoryPids) {
+ addValues(this.factoryPids, factoryPids);
+ }
+
+ protected void removeFactoryPid(String factoryPid) {
+ this.factoryPids.remove(factoryPid);
+ }
+
+ protected void addMetaTypeProvider(String key, MetaTypeProvider mtp) {
+ if (key != null && mtp != null) {
+ metaTypeProviders.put(key, mtp);
+ locales = null;
+ }
+ }
+
+ protected MetaTypeProvider removeMetaTypeProvider(String key) {
+ if (key != null) {
+ locales = null;
+ return (MetaTypeProvider) metaTypeProviders.remove(key);
+ }
+
+ return null;
+ }
+
+ private void addValues(Collection dest, Object[] values) {
+ if (values != null && values.length > 0) {
+ dest.addAll(Arrays.asList(values));
+ }
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/internal/MetaTypeServiceImpl.java b/metatype/src/main/java/org/apache/felix/metatype/internal/MetaTypeServiceImpl.java
new file mode 100644
index 0000000..4501eab
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/internal/MetaTypeServiceImpl.java
@@ -0,0 +1,118 @@
+/*
+ * 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.metatype.internal;
+
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+
+import org.apache.felix.metatype.MetaData;
+import org.apache.felix.metatype.MetaDataReader;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+import org.osgi.service.metatype.MetaTypeInformation;
+import org.osgi.service.metatype.MetaTypeService;
+import org.xmlpull.v1.XmlPullParserException;
+
+
+/**
+ * The <code>MetaTypeServiceImpl</code> class is the implementation of the
+ * <code>MetaTypeService</code> interface of the OSGi Metatype Service
+ * Specification 1.1.
+ *
+ * @author fmeschbe
+ */
+class MetaTypeServiceImpl implements MetaTypeService
+{
+
+ /** The <code>BundleContext</code> of the providing this service. */
+ private final BundleContext bundleContext;
+
+
+ /**
+ * Creates an instance of this class.
+ *
+ * @param bundleContext The <code>BundleContext</code> ultimately used to
+ * access services if there are no meta type documents.
+ */
+ MetaTypeServiceImpl( BundleContext bundleContext )
+ {
+ this.bundleContext = bundleContext;
+ }
+
+
+ /**
+ * Looks for meta type documents in the given <code>bundle</code>. If no
+ * such documents exist, a <code>MetaTypeInformation</code> object is
+ * returned handling the services of the bundle.
+ * <p>
+ * According to the specification, the services of the bundle are ignored
+ * if at least one meta type document exists.
+ *
+ * @param bundle The <code>Bundle</code> for which a
+ * <code>MetaTypeInformation</code> is to be returned.
+ */
+ public MetaTypeInformation getMetaTypeInformation( Bundle bundle )
+ {
+ MetaTypeInformation mti = fromDocuments( bundle );
+ if ( mti != null )
+ {
+ return mti;
+ }
+
+ return new ServiceMetaTypeInformation( bundleContext, bundle );
+ }
+
+
+ private MetaTypeInformation fromDocuments( Bundle bundle )
+ {
+ MetaDataReader reader = new MetaDataReader();
+
+ // get the descriptors, return nothing if none
+ Enumeration docs = bundle.findEntries( METATYPE_DOCUMENTS_LOCATION, "*.xml", false );
+ if ( docs == null || !docs.hasMoreElements() )
+ {
+ return null;
+ }
+
+ MetaTypeInformationImpl cmti = new MetaTypeInformationImpl( bundle );
+ while ( docs.hasMoreElements() )
+ {
+ URL doc = ( URL ) docs.nextElement();
+ try
+ {
+ MetaData metaData = reader.parse( doc );
+ if (metaData != null) {
+ cmti.addMetaData( metaData );
+ }
+ }
+ catch ( XmlPullParserException xppe )
+ {
+ Activator.log( LogService.LOG_ERROR, "fromDocuments: Error parsing document " + doc, xppe );
+ }
+ catch ( IOException ioe )
+ {
+ Activator.log( LogService.LOG_ERROR, "fromDocuments: Error accessing document " + doc, ioe );
+ }
+ }
+ return cmti;
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/internal/ServiceMetaTypeInformation.java b/metatype/src/main/java/org/apache/felix/metatype/internal/ServiceMetaTypeInformation.java
new file mode 100644
index 0000000..30a353e
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/internal/ServiceMetaTypeInformation.java
@@ -0,0 +1,238 @@
+/*
+ * 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.metatype.internal;
+
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+import org.osgi.service.metatype.MetaTypeProvider;
+
+
+/**
+ * The <code>ServiceMetaTypeInformation</code> extends the
+ * {@link CompoundMetaTypeInformation} adding support to register and unregister
+ * <code>ManagedService</code>s and <code>ManagedServiceFactory</code>s
+ * also implementing the <code>MetaTypeProvider</code> interface.
+ *
+ * @author fmeschbe
+ */
+public class ServiceMetaTypeInformation extends MetaTypeInformationImpl implements ServiceListener
+{
+
+ /**
+ * The filter specification to find <code>ManagedService</code>s and
+ * <code>ManagedServiceFactory</code>s as well as to register a service
+ * listener for those services (value is
+ * "(|(objectClass=org.osgi.service.cm.ManagedService)(objectClass=org.osgi.service.cm.ManagedServiceFactory))").
+ * We use the hard coded class name here to not create a dependency on the
+ * ConfigurationAdmin service, which may not be available.
+ */
+ private static final String FILTER = "(|(objectClass=org.osgi.service.cm.ManagedService)(objectClass=org.osgi.service.cm.ManagedServiceFactory))";
+
+ /**
+ * The <code>BundleContext</code> used to get and unget services which
+ * have to be registered and unregistered with the base class.
+ */
+ private final BundleContext bundleContext;
+
+
+ /**
+ * Creates an instance of this class handling services of the given
+ * <code>bundle</code>.
+ *
+ * @param bundleContext The <code>BundleContext</code> used to get and
+ * unget services.
+ * @param bundle The <code>Bundle</code> whose services are handled by
+ * this class.
+ */
+ public ServiceMetaTypeInformation( BundleContext bundleContext, Bundle bundle )
+ {
+ super( bundle );
+
+ this.bundleContext = bundleContext;
+
+ // register for service events for the bundle
+ try
+ {
+ bundleContext.addServiceListener( this, FILTER );
+ }
+ catch ( InvalidSyntaxException ise )
+ {
+ Activator.log( LogService.LOG_ERROR, "ServiceMetaTypeInformation: Cannot register for service events", ise );
+ }
+
+ // prepare the filter to select existing services
+ Filter filter;
+ try
+ {
+ filter = bundleContext.createFilter( FILTER );
+ }
+ catch ( InvalidSyntaxException ise )
+ {
+ Activator.log( LogService.LOG_ERROR, "ServiceMetaTypeInformation: Cannot create filter '" + FILTER + "'",
+ ise );
+ return;
+ }
+
+ // add current services of the bundle
+ ServiceReference[] sr = bundle.getRegisteredServices();
+ if ( sr != null )
+ {
+ for ( int i = 0; i < sr.length; i++ )
+ {
+ if ( filter.match( sr[i] ) )
+ {
+ addService( sr[i] );
+ }
+ }
+ }
+ }
+
+
+ // ---------- ServiceListener ----------------------------------------------
+
+ /**
+ * Handles service registration and unregistration events ignoring all
+ * services not belonging to the <code>Bundle</code> which is handled by
+ * this instance.
+ *
+ * @param event The <code>ServiceEvent</code>
+ */
+ public void serviceChanged( ServiceEvent event )
+ {
+ // only care for services of our bundle
+ if ( !getBundle().equals( event.getServiceReference().getBundle() ) )
+ {
+ return;
+ }
+
+ if ( event.getType() == ServiceEvent.REGISTERED )
+ {
+ addService( event.getServiceReference() );
+ }
+ else if ( event.getType() == ServiceEvent.UNREGISTERING )
+ {
+ removeService( event.getServiceReference() );
+ }
+ }
+
+
+ /**
+ * Registers the service described by the <code>serviceRef</code> with
+ * this instance if the service is a <code>MetaTypeProvider</code>
+ * instance and either a <code>service.factoryPid</code> or
+ * <code>service.pid</code> property is set in the service registration
+ * properties.
+ * <p>
+ * If the service is registered, this bundle keeps a reference, which is
+ * ungot when the service is unregistered or this bundle is stopped.
+ *
+ * @param serviceRef The <code>ServiceReference</code> describing the
+ * service to be checked and handled.
+ */
+ protected void addService( ServiceReference serviceRef )
+ {
+ Object srv = bundleContext.getService( serviceRef );
+
+ boolean ungetService = true;
+
+ if ( srv instanceof MetaTypeProvider )
+ {
+ MetaTypeProvider mtp = ( MetaTypeProvider ) srv;
+
+ // 1. check for a service factory PID
+ String factoryPid = ( String ) serviceRef.getProperty( SERVICE_FACTORYPID );
+ if ( factoryPid != null )
+ {
+ addFactoryPids( new String[]
+ { factoryPid } );
+ addMetaTypeProvider( factoryPid, mtp );
+ ungetService = false;
+ }
+ else
+ {
+ // 2. check for a service PID
+ String pid = ( String ) serviceRef.getProperty( Constants.SERVICE_PID );
+ if ( pid != null )
+ {
+ addPids( new String[]
+ { pid } );
+ addMetaTypeProvider( pid, mtp );
+ ungetService = false;
+ }
+ }
+ }
+
+ if ( ungetService )
+ {
+ bundleContext.ungetService( serviceRef );
+ }
+ }
+
+
+ /**
+ * Unregisters the service described by the <code>serviceRef</code> from
+ * this instance. Unregistration just checks for the
+ * <code>service.factoryPid</code> and <code>service.pid</code> service
+ * properties but does not care whether the service implements the
+ * <code>MetaTypeProvider</code> interface. If the service is registered
+ * it is simply unregistered.
+ * <p>
+ * If the service is actually unregistered the reference retrieved by the
+ * registration method is ungotten.
+ *
+ * @param serviceRef The <code>ServiceReference</code> describing the
+ * service to be unregistered.
+ */
+ protected void removeService( ServiceReference serviceRef )
+ {
+ boolean ungetService = false;
+
+ // 1. check for a service factory PID
+ String factoryPid = ( String ) serviceRef.getProperty( SERVICE_FACTORYPID );
+ if ( factoryPid != null )
+ {
+ ungetService = removeMetaTypeProvider( factoryPid ) != null;
+ removeFactoryPid( factoryPid );
+ }
+ else
+ {
+ // 2. check for a service PID
+ String pid = ( String ) serviceRef.getProperty( Constants.SERVICE_PID );
+ if ( pid != null )
+ {
+ ungetService = removeMetaTypeProvider( pid ) != null;
+ removePid( pid );
+ }
+ }
+
+ // 3. drop the service reference
+ if ( ungetService )
+ {
+ bundleContext.ungetService( serviceRef );
+ }
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/internal/l10n/BundleResources.java b/metatype/src/main/java/org/apache/felix/metatype/internal/l10n/BundleResources.java
new file mode 100644
index 0000000..7834c50
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/internal/l10n/BundleResources.java
@@ -0,0 +1,221 @@
+/*
+ * 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.metatype.internal.l10n;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.osgi.framework.Bundle;
+
+
+/**
+ * The <code>BundleResources</code> TODO
+ *
+ * @author fmeschbe
+ * @version $Rev:$, $Date:$
+ */
+public class BundleResources
+{
+
+ private Bundle bundle;
+ private long bundleLastModified;
+
+ private Map resourcesByLocale;
+
+ private static Map resourcesByBundle = null;
+
+
+ public static Resources getResources( Bundle bundle, String basename, String locale )
+ {
+ BundleResources bundleResources = null;
+
+ if ( resourcesByBundle != null )
+ {
+ // the bundle has been uninstalled, ensure removed from the cache
+ // and return null (e.g. no resources now)
+ if ( bundle.getState() == Bundle.UNINSTALLED )
+ {
+ resourcesByBundle.remove( new Long( bundle.getBundleId() ) );
+ return null;
+ }
+
+ // else check whether we know the bundle already
+ bundleResources = ( BundleResources ) resourcesByBundle.get( new Long( bundle.getBundleId() ) );
+ }
+ else
+ {
+ // create the cache to be used for a newly created BundleResources
+ resourcesByBundle = new HashMap();
+ }
+
+ if ( bundleResources == null )
+ {
+ bundleResources = new BundleResources( bundle );
+ resourcesByBundle.put( new Long( bundle.getBundleId() ), bundleResources );
+ }
+
+ return bundleResources.getResources( basename, locale );
+ }
+
+
+ public static void clearResourcesCache()
+ {
+ resourcesByBundle = null;
+ }
+
+
+ private BundleResources( Bundle bundle )
+ {
+ this.bundle = bundle;
+ this.bundleLastModified = bundle.getLastModified();
+ this.resourcesByLocale = new HashMap();
+ }
+
+
+ private boolean isUpToDate()
+ {
+ return bundle.getState() != Bundle.UNINSTALLED && bundleLastModified >= bundle.getLastModified();
+ }
+
+
+ private Resources getResources( String basename, String locale )
+ {
+ // ensure locale - use VM default locale if null
+ if ( locale == null )
+ {
+ locale = Locale.getDefault().toString();
+ }
+
+ // check the cache, if the bundle has not changed
+ if ( isUpToDate() )
+ {
+ Resources res = ( Resources ) resourcesByLocale.get( locale );
+ if ( res != null )
+ {
+ return res;
+ }
+ }
+ else
+ {
+ // otherwise clear the cache
+ resourcesByLocale.clear();
+ }
+
+ // get the list of potential resource names files
+ Properties parentProperties = null;
+ List resList = createResourceList( locale );
+ for ( Iterator ri = resList.iterator(); ri.hasNext(); )
+ {
+ String tmpLocale = ( String ) ri.next();
+ Resources res = ( Resources ) resourcesByLocale.get( tmpLocale );
+ if ( res != null )
+ {
+ parentProperties = res.getResources();
+ }
+ else
+ {
+ Properties props = loadProperties( basename, tmpLocale, parentProperties );
+ res = new Resources( tmpLocale, props );
+ resourcesByLocale.put( tmpLocale, res );
+ parentProperties = props;
+ }
+ }
+
+ // just return from the cache again
+ return ( Resources ) resourcesByLocale.get( locale );
+ }
+
+
+ private Properties loadProperties( String basename, String locale, Properties parentProperties )
+ {
+ String resourceName = basename;
+ if ( locale != null && locale.length() > 0 )
+ {
+ resourceName += "_" + locale;
+ }
+ resourceName += ".properties";
+
+ Properties props = new Properties( parentProperties );
+ URL resURL = bundle.getEntry( resourceName );
+ if ( resURL != null )
+ {
+ InputStream ins = null;
+ try
+ {
+ ins = resURL.openStream();
+ props.load( ins );
+ }
+ catch ( IOException ex )
+ {
+ // File doesn't exist, just continue loop
+ }
+ finally
+ {
+ if ( ins != null )
+ {
+ try
+ {
+ ins.close();
+ }
+ catch ( IOException ignore )
+ {
+ }
+ }
+ }
+ }
+
+ return props;
+ }
+
+
+ private List createResourceList( String locale )
+ {
+ List result = new ArrayList( 4 );
+
+ StringTokenizer tokens;
+ StringBuffer tempLocale = new StringBuffer();
+
+ result.add( tempLocale.toString() );
+
+ if ( locale != null && locale.length() > 0 )
+ {
+ tokens = new StringTokenizer( locale, "_" );
+ while ( tokens.hasMoreTokens() )
+ {
+ if ( tempLocale.length() > 0 )
+ {
+ tempLocale.append( "_" );
+ }
+ tempLocale.append( tokens.nextToken() );
+ result.add( tempLocale.toString() );
+ }
+ }
+ return result;
+ }
+}
diff --git a/metatype/src/main/java/org/apache/felix/metatype/internal/l10n/Resources.java b/metatype/src/main/java/org/apache/felix/metatype/internal/l10n/Resources.java
new file mode 100644
index 0000000..7b544dc
--- /dev/null
+++ b/metatype/src/main/java/org/apache/felix/metatype/internal/l10n/Resources.java
@@ -0,0 +1,50 @@
+/*
+ * 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.metatype.internal.l10n;
+
+import java.util.Properties;
+
+/**
+ * The <code>Resources</code> TODO
+ *
+ * @author fmeschbe
+ */
+public class Resources
+{
+
+ private String locale;
+ private Properties resources;
+
+ Resources(String locale, Properties resources) {
+ this.locale = locale;
+ this.resources = resources;
+ }
+
+ public String getLocale() {
+ return locale;
+ }
+
+ Properties getResources() {
+ return resources;
+ }
+
+ public String getResource(String resourceName) {
+ return resources.getProperty( resourceName, resourceName );
+ }
+}
diff --git a/metatype/src/test/java/org/apache/felix/metatype/ADTest.java b/metatype/src/test/java/org/apache/felix/metatype/ADTest.java
new file mode 100644
index 0000000..ecad68d
--- /dev/null
+++ b/metatype/src/test/java/org/apache/felix/metatype/ADTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.metatype;
+
+
+import junit.framework.TestCase;
+
+import org.osgi.service.metatype.AttributeDefinition;
+
+
+/**
+ * The <code>ADTest</code> class tests the static helper methods of the
+ * {@link AD} class.
+ *
+ * @author fmeschbe
+ */
+public class ADTest extends TestCase
+{
+
+ private static final String BLANK = " \r\n \t";
+
+
+ public void testNull()
+ {
+ String listString = null;
+ String[] list = AD.splitList( listString );
+ assertNull( list );
+ }
+
+
+ public void testEmpty()
+ {
+ String listString = "";
+ String[] list = AD.splitList( listString );
+ assertNull( list );
+ }
+
+
+ public void testSingle()
+ {
+ String value0 = "value";
+ String listString = value0;
+ String[] list = AD.splitList( listString );
+ assertNotNull( list );
+ assertEquals( 1, list.length );
+ assertEquals( value0, list[0] );
+ }
+
+
+ public void testTwo()
+ {
+ String value0 = "value0";
+ String value1 = "value1";
+ String listString = value0 + "," + value1;
+ String[] list = AD.splitList( listString );
+ assertNotNull( list );
+ assertEquals( 2, list.length );
+ assertEquals( value0, list[0] );
+ assertEquals( value1, list[1] );
+ }
+
+
+ public void testSingleBlanks()
+ {
+ String value0 = "value";
+ String listString = BLANK + value0 + BLANK;
+ String[] list = AD.splitList( listString );
+ assertNotNull( list );
+ assertEquals( 1, list.length );
+ assertEquals( value0, list[0] );
+ }
+
+
+ public void testTwoBlanks()
+ {
+ String value0 = "value0";
+ String value1 = "value1";
+ String listString = BLANK + value0 + BLANK + "," + BLANK + value1 + BLANK;
+ String[] list = AD.splitList( listString );
+ assertNotNull( list );
+ assertEquals( 2, list.length );
+ assertEquals( value0, list[0] );
+ assertEquals( value1, list[1] );
+ }
+
+
+ public void testStandardSample()
+ {
+ String value0 = "a,b";
+ String value1 = "b,c";
+ String value2 = "c\\";
+ String value3 = "d";
+ String listString = "a\\,b,b\\,c, c\\\\,d";
+ String[] list = AD.splitList( listString );
+ assertNotNull( list );
+ assertEquals( 4, list.length );
+ assertEquals( value0, list[0] );
+ assertEquals( value1, list[1] );
+ assertEquals( value2, list[2] );
+ assertEquals( value3, list[3] );
+ }
+
+
+ public void testToTypeString()
+ {
+ assertEquals( AttributeDefinition.STRING, AD.toType( "String" ) );
+ assertEquals( AttributeDefinition.LONG, AD.toType( "Long" ) );
+ assertEquals( AttributeDefinition.DOUBLE, AD.toType( "Double" ) );
+ assertEquals( AttributeDefinition.FLOAT, AD.toType( "Float" ) );
+ assertEquals( AttributeDefinition.INTEGER, AD.toType( "Integer" ) );
+ assertEquals( AttributeDefinition.BYTE, AD.toType( "Byte" ) );
+ assertEquals( AttributeDefinition.CHARACTER, AD.toType( "Char" ) );
+ assertEquals( AttributeDefinition.BOOLEAN, AD.toType( "Boolean" ) );
+ assertEquals( AttributeDefinition.SHORT, AD.toType( "Short" ) );
+ assertEquals( AttributeDefinition.STRING, AD.toType( "JohnDoe" ) );
+ }
+}
diff --git a/metatype/src/test/java/org/apache/felix/metatype/MetaDataReaderTest.java b/metatype/src/test/java/org/apache/felix/metatype/MetaDataReaderTest.java
new file mode 100644
index 0000000..0a1f2ed
--- /dev/null
+++ b/metatype/src/test/java/org/apache/felix/metatype/MetaDataReaderTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.metatype;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
+
+import org.osgi.service.metatype.AttributeDefinition;
+import org.xmlpull.v1.XmlPullParserException;
+
+
+/**
+ * The <code>MetaDataReaderTest</code> class tests the
+ * <code>MetaDataReader</code> class.
+ *
+ * @author fmeschbe
+ */
+public class MetaDataReaderTest extends TestCase
+{
+
+ private MetaDataReader reader;
+
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ reader = new MetaDataReader();
+ }
+
+
+ protected void tearDown() throws Exception
+ {
+ reader = null;
+
+ super.tearDown();
+ }
+
+
+ public void testEmpty() throws IOException, XmlPullParserException
+ {
+ String empty = "<MetaData />";
+ MetaData mti = read( empty );
+
+ assertNull( mti.getLocalePrefix() );
+ assertNull( mti.getObjectClassDefinitions() );
+ }
+
+
+ public void testEmptyLocalization() throws IOException, XmlPullParserException
+ {
+ String testLoc = "OSGI-INF/folder/base";
+ String empty = "<MetaData localization=\"" + testLoc + "\"/>";
+ MetaData mti = read( empty );
+
+ assertEquals( testLoc, mti.getLocalePrefix() );
+ }
+
+
+ public void testSingleEmptyOCD() throws IOException, XmlPullParserException
+ {
+ String ocdName = "ocd0";
+ String ocdId = "id.ocd0";
+ String ocdDescription = "ocd0 description";
+
+ String empty = "<MetaData><OCD id=\"" + ocdId + "\" name=\"" + ocdName + "\" description=\"" + ocdDescription
+ + "\" /></MetaData>";
+ MetaData mti = read( empty );
+
+ assertNull( mti.getLocalePrefix() );
+ assertNotNull( mti.getObjectClassDefinitions() );
+ assertEquals( 1, mti.getObjectClassDefinitions().size() );
+
+ OCD ocd = ( OCD ) mti.getObjectClassDefinitions().values().iterator().next();
+ assertEquals( ocdId, ocd.getID() );
+ assertEquals( ocdName, ocd.getName() );
+ assertEquals( ocdDescription, ocd.getDescription() );
+
+ assertNull( ocd.getAttributeDefinitions() );
+ }
+
+
+ public void testSingleOCDSingleRequiredAttr() throws IOException, XmlPullParserException
+ {
+ String ocdName = "ocd0";
+ String ocdId = "id.ocd0";
+ String ocdDescription = "ocd0 description";
+
+ String adId = "id.ad0";
+ String adName = "ad0";
+ String adDescription = "ad0 description";
+ String adType = "String";
+ int adCardinality = 789;
+ String adDefault = " a , b , c ";
+
+ String empty = "<MetaData>" + "<OCD id=\"" + ocdId + "\" name=\"" + ocdName + "\" description=\""
+ + ocdDescription + "\">" + "<AD id=\"" + adId + "\" name=\"" + adName + "\" type=\"" + adType
+ + "\" description=\"" + adDescription + "\" cardinality=\"" + adCardinality + "\" default=\"" + adDefault
+ + "\">" + "</AD>" + "</OCD>" + "</MetaData>";
+ MetaData mti = read( empty );
+
+ assertNull( mti.getLocalePrefix() );
+ assertNotNull( mti.getObjectClassDefinitions() );
+ assertEquals( 1, mti.getObjectClassDefinitions().size() );
+
+ OCD ocd = ( OCD ) mti.getObjectClassDefinitions().values().iterator().next();
+
+ assertNotNull( ocd.getAttributeDefinitions() );
+ assertEquals( 1, ocd.getAttributeDefinitions().size() );
+
+ AD ad = ( AD ) ocd.getAttributeDefinitions().values().iterator().next();
+ assertEquals( adId, ad.getID() );
+ assertEquals( adName, ad.getName() );
+ assertEquals( adDescription, ad.getDescription() );
+ assertEquals( AttributeDefinition.STRING, ad.getType() );
+ assertEquals( adCardinality, ad.getCardinality() );
+ assertNotNull( ad.getDefaultValue() );
+ assertEquals( 3, ad.getDefaultValue().length );
+
+ String[] defaultValue = ad.getDefaultValue();
+ assertEquals( "a", defaultValue[0] );
+ assertEquals( "b", defaultValue[1] );
+ assertEquals( "c", defaultValue[2] );
+ }
+
+
+ private MetaData read( String data ) throws IOException, XmlPullParserException
+ {
+ InputStream input = new ByteArrayInputStream( data.getBytes( "UTF-8" ) );
+ return reader.parse( input );
+ }
+}
diff --git a/metatype/src/test/java/org/apache/felix/metatype/MockBundle.java b/metatype/src/test/java/org/apache/felix/metatype/MockBundle.java
new file mode 100644
index 0000000..cf6fc71
--- /dev/null
+++ b/metatype/src/test/java/org/apache/felix/metatype/MockBundle.java
@@ -0,0 +1,189 @@
+/*
+ * 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.metatype;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.NoSuchElementException;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+
+public class MockBundle implements Bundle
+{
+
+ private BundleContext bundleContext;
+ private long bundleId;
+ private String bundleSymbolicName;
+ private Hashtable headers = new Hashtable();
+
+
+ MockBundle( BundleContext bundleContext, long bundleId, String bundleSymbolicName )
+ {
+ this.bundleContext = bundleContext;
+ this.bundleId = bundleId;
+ this.bundleSymbolicName = bundleSymbolicName;
+ }
+
+
+ public BundleContext getBundleContext()
+ {
+ return bundleContext;
+ }
+
+
+ public Enumeration findEntries( String path, String filePattern, boolean recurse )
+ {
+ return new Enumeration()
+ {
+ public boolean hasMoreElements()
+ {
+ return false;
+ }
+
+
+ public java.lang.Object nextElement()
+ {
+ throw new NoSuchElementException();
+ }
+ };
+ }
+
+
+ public long getBundleId()
+ {
+ return bundleId;
+ }
+
+
+ public URL getEntry( String name )
+ {
+ return getResource( name );
+ }
+
+
+ public Enumeration getEntryPaths( String path )
+ {
+ return null;
+ }
+
+
+ public Dictionary getHeaders()
+ {
+ return headers;
+ }
+
+
+ public Dictionary getHeaders( String locale )
+ {
+ return headers;
+ }
+
+
+ public long getLastModified()
+ {
+ return 0;
+ }
+
+
+ public String getLocation()
+ {
+ return "mock";
+ }
+
+
+ public ServiceReference[] getRegisteredServices()
+ {
+ return null;
+ }
+
+
+ public URL getResource( String name )
+ {
+ return getClass().getClassLoader().getResource( name );
+ }
+
+
+ public Enumeration getResources( String name ) throws IOException
+ {
+ return getClass().getClassLoader().getResources( name );
+ }
+
+
+ public ServiceReference[] getServicesInUse()
+ {
+ return null;
+ }
+
+
+ public int getState()
+ {
+ return Bundle.ACTIVE;
+ }
+
+
+ public String getSymbolicName()
+ {
+ return bundleSymbolicName;
+ }
+
+
+ public boolean hasPermission( java.lang.Object permission )
+ {
+ return true;
+ }
+
+
+ public Class loadClass( String name ) throws ClassNotFoundException
+ {
+ return getClass().getClassLoader().loadClass( name );
+ }
+
+
+ public void start()
+ {
+ }
+
+
+ public void stop()
+ {
+ }
+
+
+ public void uninstall()
+ {
+ }
+
+
+ public void update()
+ {
+ }
+
+
+ public void update( InputStream in )
+ {
+ }
+}
diff --git a/metatype/src/test/java/org/apache/felix/metatype/MockBundleContext.java b/metatype/src/test/java/org/apache/felix/metatype/MockBundleContext.java
new file mode 100644
index 0000000..21659d3
--- /dev/null
+++ b/metatype/src/test/java/org/apache/felix/metatype/MockBundleContext.java
@@ -0,0 +1,432 @@
+/*
+ * 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.metatype;
+
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+
+/**
+ * The <code>MockBundleContext</code> TODO
+ *
+ * @author fmeschbe
+ * @version $Rev:$, $Date:$
+ */
+public class MockBundleContext implements BundleContext
+{
+
+ private Bundle theBundle;
+ private Map services;
+
+ private Set serviceListeners;
+
+
+ public MockBundleContext( long bundleId, String bundleSymbolicName )
+ {
+ theBundle = new MockBundle( this, bundleId, bundleSymbolicName );
+ services = new HashMap();
+ serviceListeners = new HashSet();
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#addBundleListener(org.osgi.framework.BundleListener)
+ */
+ public void addBundleListener( BundleListener arg0 )
+ {
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#addFrameworkListener(org.osgi.framework.FrameworkListener)
+ */
+ public void addFrameworkListener( FrameworkListener arg0 )
+ {
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#addServiceListener(org.osgi.framework.ServiceListener)
+ */
+ public void addServiceListener( ServiceListener listener )
+ {
+ serviceListeners.add( listener );
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#addServiceListener(org.osgi.framework.ServiceListener, java.lang.String)
+ */
+ public void addServiceListener( ServiceListener listener, String filter )
+ {
+ serviceListeners.add( listener );
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#createFilter(java.lang.String)
+ */
+ public Filter createFilter( String arg0 )
+ {
+ return new Filter()
+ {
+
+ public boolean match( ServiceReference arg0 )
+ {
+ return true;
+ }
+
+
+ public boolean match( Dictionary arg0 )
+ {
+ return true;
+ }
+
+
+ public boolean matchCase( Dictionary arg0 )
+ {
+ return true;
+ }
+
+ };
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#getAllServiceReferences(java.lang.String, java.lang.String)
+ */
+ public ServiceReference[] getAllServiceReferences( String arg0, String arg1 )
+ {
+ return null;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#getBundle()
+ */
+ public Bundle getBundle()
+ {
+ return theBundle;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#getBundle(long)
+ */
+ public Bundle getBundle( long bundleId )
+ {
+ if ( bundleId == getBundle().getBundleId() )
+ {
+ return getBundle();
+ }
+
+ return null;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#getBundles()
+ */
+ public Bundle[] getBundles()
+ {
+ return new Bundle[]
+ { getBundle() };
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#getDataFile(java.lang.String)
+ */
+ public File getDataFile( String arg0 )
+ {
+ return null;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#getProperty(java.lang.String)
+ */
+ public String getProperty( String arg0 )
+ {
+ return null;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#getService(org.osgi.framework.ServiceReference)
+ */
+ public Object getService( ServiceReference serviceReference )
+ {
+ if ( serviceReference instanceof MockServiceReference )
+ {
+ return ( ( MockServiceReference ) serviceReference ).getServiceRegistration().getService();
+ }
+
+ return null;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#getServiceReference(java.lang.String)
+ */
+ public ServiceReference getServiceReference( String name )
+ {
+ ServiceRegistration sr = ( ServiceRegistration ) services.get( name );
+ return ( sr != null ) ? sr.getReference() : null;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#getServiceReferences(java.lang.String, java.lang.String)
+ */
+ public ServiceReference[] getServiceReferences( String arg0, String arg1 )
+ {
+ return null;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#installBundle(java.lang.String)
+ */
+ public Bundle installBundle( String arg0 )
+ {
+ return null;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#installBundle(java.lang.String, java.io.InputStream)
+ */
+ public Bundle installBundle( String arg0, InputStream arg1 )
+ {
+ return null;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#registerService(java.lang.String[], java.lang.Object, java.util.Dictionary)
+ */
+ public ServiceRegistration registerService( String[] names, Object service, Dictionary props )
+ {
+ props.put( Constants.OBJECTCLASS, names );
+ ServiceRegistration sr = new MockServiceRegistration( this, service, names, props );
+
+ for ( int i = 0; i < names.length; i++ )
+ {
+ services.put( names[i], sr );
+ }
+
+ fireServiceEvent( sr.getReference(), ServiceEvent.REGISTERED );
+
+ return sr;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#registerService(java.lang.String, java.lang.Object, java.util.Dictionary)
+ */
+ public ServiceRegistration registerService( String name, Object service, Dictionary props )
+ {
+ return registerService( new String[]
+ { name }, service, props );
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#removeBundleListener(org.osgi.framework.BundleListener)
+ */
+ public void removeBundleListener( BundleListener arg0 )
+ {
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#removeFrameworkListener(org.osgi.framework.FrameworkListener)
+ */
+ public void removeFrameworkListener( FrameworkListener arg0 )
+ {
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#removeServiceListener(org.osgi.framework.ServiceListener)
+ */
+ public void removeServiceListener( ServiceListener listener )
+ {
+ serviceListeners.remove( listener );
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleContext#ungetService(org.osgi.framework.ServiceReference)
+ */
+ public boolean ungetService( ServiceReference serviceReference )
+ {
+ if ( serviceReference instanceof MockServiceReference )
+ {
+ return ( ( MockServiceReference ) serviceReference ).getServiceRegistration().ungetService();
+
+ }
+
+ return false;
+ }
+
+
+ private void fireServiceEvent( ServiceReference ref, int type )
+ {
+ ServiceEvent se = new ServiceEvent( type, ref );
+ for ( Iterator li = serviceListeners.iterator(); li.hasNext(); )
+ {
+ ( ( ServiceListener ) li.next() ).serviceChanged( se );
+ }
+ }
+
+ private static class MockServiceRegistration implements ServiceRegistration
+ {
+
+ private MockBundleContext bundleContext;
+ private Dictionary serviceProps;
+ private String[] serviceNames;
+ private Object service;
+ private ServiceReference serviceRef;
+ int refs;
+
+
+ MockServiceRegistration( MockBundleContext bundleContext, Object service, String[] names, Dictionary props )
+ {
+ this.bundleContext = bundleContext;
+ this.serviceNames = names;
+ this.serviceProps = props;
+ this.service = service;
+ this.serviceRef = new MockServiceReference( this );
+ }
+
+
+ Object getService()
+ {
+ refs++;
+ return service;
+ }
+
+
+ boolean ungetService()
+ {
+ refs--;
+ return refs <= 0;
+ }
+
+
+ public ServiceReference getReference()
+ {
+ return serviceRef;
+ }
+
+
+ public void setProperties( Dictionary props )
+ {
+ serviceProps = props;
+ }
+
+
+ public void unregister()
+ {
+ bundleContext.fireServiceEvent( getReference(), ServiceEvent.UNREGISTERING );
+
+ for ( int i = 0; i < serviceNames.length; i++ )
+ {
+ bundleContext.services.remove( serviceNames[i] );
+ }
+ }
+
+ };
+
+ private static class MockServiceReference implements ServiceReference
+ {
+ private MockServiceRegistration msr;
+
+
+ MockServiceReference( MockServiceRegistration msr )
+ {
+ this.msr = msr;
+ }
+
+
+ MockServiceRegistration getServiceRegistration()
+ {
+ return msr;
+ }
+
+
+ public Bundle getBundle()
+ {
+ return msr.bundleContext.getBundle();
+ }
+
+
+ public Object getProperty( String prop )
+ {
+ return msr.serviceProps.get( prop );
+ }
+
+
+ public String[] getPropertyKeys()
+ {
+ List keys = new ArrayList();
+ for ( Enumeration ke = msr.serviceProps.keys(); ke.hasMoreElements(); )
+ {
+ keys.add( ke.nextElement() );
+ }
+ return ( String[] ) keys.toArray( new String[keys.size()] );
+ }
+
+
+ public Bundle[] getUsingBundles()
+ {
+ return null;
+ }
+
+
+ public boolean isAssignableTo( Bundle arg0, String arg1 )
+ {
+ return false;
+ }
+
+ }
+}
diff --git a/metatype/src/test/java/org/apache/felix/metatype/internal/MetaTypeServiceImplTest.java b/metatype/src/test/java/org/apache/felix/metatype/internal/MetaTypeServiceImplTest.java
new file mode 100644
index 0000000..69a57ef
--- /dev/null
+++ b/metatype/src/test/java/org/apache/felix/metatype/internal/MetaTypeServiceImplTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.metatype.internal;
+
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.metatype.MockBundleContext;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.metatype.MetaTypeInformation;
+import org.osgi.service.metatype.MetaTypeProvider;
+import org.osgi.service.metatype.MetaTypeService;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+
+/**
+ * The <code>MetaTypeServiceImplTest</code> class tests the
+ * {@link MetaTypeServiceImpl}.
+ *
+ * @author fmeschbe
+ */
+public class MetaTypeServiceImplTest extends TestCase
+{
+
+ BundleContext bundleContext;
+
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ bundleContext = new MockBundleContext( 10, "org.apache.felix.metatype.Mock" );
+ bundleContext.getBundle().start();
+ }
+
+
+ protected void tearDown() throws Exception
+ {
+ bundleContext.getBundle().stop();
+ bundleContext = null;
+
+ super.tearDown();
+ }
+
+
+ public void testEmpty()
+ {
+ MetaTypeService mts = new MetaTypeServiceImpl( bundleContext );
+ MetaTypeInformation mti = mts.getMetaTypeInformation( bundleContext.getBundle() );
+ checkEmpty( mti );
+ }
+
+
+ public void testAfterCretionManagedService()
+ {
+ MetaTypeService mts = new MetaTypeServiceImpl( bundleContext );
+ MetaTypeInformation mti = mts.getMetaTypeInformation( bundleContext.getBundle() );
+
+ // assert still empty
+ checkEmpty( mti );
+
+ // register a service with PID
+ String pid = "testAfterCreation";
+ MockManagedService service = new MockManagedService();
+ Dictionary props = new Hashtable();
+ props.put( Constants.SERVICE_PID, pid );
+ ServiceRegistration sr = bundleContext.registerService( ManagedService.class.getName(), service, props );
+
+ // locales should contain MockMetaTypeProvider.LOCALES
+ assertNotNull( mti.getLocales() );
+ assertTrue( mti.getLocales().length == 1 );
+ assertEquals( MockMetaTypeProvider.LOCALES[0], mti.getLocales()[0] );
+
+ // pids must contain pid
+ assertNotNull( mti.getPids() );
+ assertTrue( mti.getPids().length == 1 );
+ assertEquals( pid, mti.getPids()[0] );
+
+ // factoryPids must be empty
+ assertTrue( mti.getFactoryPids() == null || mti.getFactoryPids().length == 0 );
+
+ // unregister the service
+ sr.unregister();
+
+ // ensure everything is clear now again
+ checkEmpty( mti );
+ }
+
+
+ public void testAfterCretionManagedServiceFactory()
+ {
+ MetaTypeService mts = new MetaTypeServiceImpl( bundleContext );
+ MetaTypeInformation mti = mts.getMetaTypeInformation( bundleContext.getBundle() );
+
+ // assert still empty
+ checkEmpty( mti );
+
+ // register a service with PID
+ String pid = "testAfterCreation";
+ String factoryPid = "testAfterCreation_factory";
+ MockManagedServiceFactory service = new MockManagedServiceFactory();
+ Dictionary props = new Hashtable();
+ props.put( Constants.SERVICE_PID, pid );
+ props.put( ConfigurationAdmin.SERVICE_FACTORYPID, factoryPid );
+ ServiceRegistration sr = bundleContext.registerService( ManagedService.class.getName(), service, props );
+
+ // locales should contain MockMetaTypeProvider.LOCALES
+ assertNotNull( mti.getLocales() );
+ assertTrue( mti.getLocales().length == 1 );
+ assertEquals( MockMetaTypeProvider.LOCALES[0], mti.getLocales()[0] );
+
+ // pids must be empty
+ assertTrue( mti.getPids() == null || mti.getPids().length == 0 );
+
+ // pids must contain pid
+ assertNotNull( mti.getFactoryPids() );
+ assertTrue( mti.getFactoryPids().length == 1 );
+ assertEquals( factoryPid, mti.getFactoryPids()[0] );
+
+ // unregister the service
+ sr.unregister();
+
+ // ensure everything is clear now again
+ checkEmpty( mti );
+ }
+
+
+ private void checkEmpty( MetaTypeInformation mti )
+ {
+ assertEquals( bundleContext.getBundle().getBundleId(), mti.getBundle().getBundleId() );
+ assertTrue( mti.getLocales() == null || mti.getLocales().length == 0 );
+ assertTrue( mti.getPids() == null || mti.getPids().length == 0 );
+ assertTrue( mti.getFactoryPids() == null || mti.getFactoryPids().length == 0 );
+ }
+
+ private static class MockMetaTypeProvider implements MetaTypeProvider
+ {
+
+ static String[] LOCALES =
+ { "en_US" };
+
+
+ public String[] getLocales()
+ {
+ return LOCALES;
+ }
+
+
+ public ObjectClassDefinition getObjectClassDefinition( String arg0, String arg1 )
+ {
+ return null;
+ }
+ }
+
+ private static class MockManagedService extends MockMetaTypeProvider implements ManagedService
+ {
+
+ public void updated( Dictionary arg0 )
+ {
+ }
+
+ }
+
+ private static class MockManagedServiceFactory extends MockMetaTypeProvider implements ManagedServiceFactory
+ {
+
+ public void deleted( String arg0 )
+ {
+ }
+
+
+ public String getName()
+ {
+ return null;
+ }
+
+
+ public void updated( String arg0, Dictionary arg1 )
+ {
+ }
+
+ }
+}