/*
 * 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.*;

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 <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
 */
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
     * @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()] );
    }


    /**
     * @see org.osgi.service.metatype.ObjectClassDefinition#getDescription()
     */
    public String getDescription()
    {
        return localize( ocd.getDescription() );
    }


    /**
     * @param size
     * @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();
    }


    /**
     * @see org.osgi.service.metatype.ObjectClassDefinition#getID()
     */
    public String getID()
    {
        return ocd.getID();
    }


    /**
     * @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;
        }

    }
}

@@ -3818,17 +3818,24 @@
 on( int 
-s
+desiredS
 ize ) th
@@ -4098,17 +4098,24 @@
 nteger( 
-s
+desiredS
 ize ) );
@@ -4111,24 +4111,25 @@
 edSize ) );%0A
+%0A
         if (
@@ -4255,16 +4255,20 @@
 selected
+Size
  = new I
@@ -4389,67 +4389,8 @@
   %7B%0A
-                Map.Entry entry = ( Map.Entry ) ei.next();%0A
     
@@ -4409,19 +4409,20 @@
 Integer 
-key
+icon
 Size = (
@@ -4437,19 +4437,14 @@
  ) e
-ntry.getKey
+i.next
 ();%0A
@@ -4464,19 +4464,20 @@
    if ( 
-key
+icon
 Size.int
@@ -4487,17 +4487,24 @@
 ue() %3C= 
-s
+desiredS
 ize && s
@@ -4510,16 +4510,20 @@
 selected
+Size
 .compare
@@ -4526,19 +4526,20 @@
 pareTo( 
-key
+icon
 Size ) %3C
@@ -4593,14 +4593,19 @@
 cted
- = key
+Size = icon
 Size
@@ -4753,16 +4753,20 @@
 selected
+Size
  );%0A    

FELIX-2867 Fix iterator to find appropriate size. The map is <Integer, String> and the iterator is iterating over the keys, hence the return iterator values are Integer (and no Map.Entry as expected in the existing code). Also renamed variables to reflect there intended value.