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

import org.apache.felix.ipojo.dependency.interceptors.TransformedServiceReference;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;

import java.util.*;

/**
 * Implements transformed service reference.
 */
public class TransformedServiceReferenceImpl<S> implements TransformedServiceReference<S> {

    private final ServiceReference<S> m_origin;
    private Map<String, Object> m_properties = new HashMap<String, Object>();

    public TransformedServiceReferenceImpl(ServiceReference<S> origin) {
        this.m_origin = origin;
        // Copy properties
        for (String key : origin.getPropertyKeys()) {
            m_properties.put(key, origin.getProperty(key));
        }
    }

    public TransformedServiceReferenceImpl<S> addProperty(String name, Object value) {
        if (FORBIDDEN_KEYS.contains(name)) {
            throw new IllegalArgumentException("Cannot change the property " + name);
        }
        m_properties.put(name, value);
        return this;
    }

    public TransformedServiceReference<S> addPropertyIfAbsent(String name, Object value) {
        if (! contains(name)) {
            addProperty(name, value);
        }
        return this;
    }

    public Object get(String name) {
        return m_properties.get(name);
    }

    public TransformedServiceReferenceImpl<S> removeProperty(String name) {
        if (FORBIDDEN_KEYS.contains(name)) {
            throw new IllegalArgumentException("Cannot change the property " + name);
        }
        // Store a null value.
        m_properties.put(name, null);
        return this;
    }

    public boolean contains(String name) {
        return m_properties.get(name) != null;
    }

    public ServiceReference<S> getWrappedReference() {
        if (m_origin instanceof TransformedServiceReferenceImpl) {
            return ((TransformedServiceReferenceImpl<S>) m_origin).getWrappedReference();
        } else {
            return m_origin;
        }
    }

    public Object getProperty(String key) {
        return m_properties.get(key);
    }

    public String[] getPropertyKeys() {
        List<String> keys = new ArrayList<String>();
        for (Map.Entry<String, Object> entry : m_properties.entrySet()) {
            if (entry.getValue() != null) {
                keys.add(entry.getKey());
            }
        }
        return keys.toArray(new String[keys.size()]);
    }

    public Bundle getBundle() {
        return m_origin.getBundle();
    }

    public Bundle[] getUsingBundles() {
        return m_origin.getUsingBundles();
    }

    public boolean isAssignableTo(Bundle bundle, String className) {
        return m_origin.isAssignableTo(bundle, className);
    }

    /**
     * Compares two service references.
     * This method is not delegated as we may have modified some of the properties using for the ranking.
     * @param reference the reference
     * @return 0, 1 or -1 depending of the reference.
     */
    public int compareTo(Object reference) {
        ServiceReference other = (ServiceReference) reference;

        Long id = (Long) getProperty(Constants.SERVICE_ID);
        Long otherId = (Long) other.getProperty(Constants.SERVICE_ID);

        if (id.equals(otherId)) {
            return 0; // same service
        }

        Object rankObj = getProperty(Constants.SERVICE_RANKING);
        Object otherRankObj = other.getProperty(Constants.SERVICE_RANKING);

        // If no rank, then spec says it defaults to zero.
        rankObj = (rankObj == null) ? new Integer(0) : rankObj;
        otherRankObj = (otherRankObj == null) ? new Integer(0) : otherRankObj;

        // If rank is not Integer, then spec says it defaults to zero.
        Integer rank = (rankObj instanceof Integer)
                ? (Integer) rankObj : new Integer(0);
        Integer otherRank = (otherRankObj instanceof Integer)
                ? (Integer) otherRankObj : new Integer(0);

        // Sort by rank in ascending order.
        if (rank.compareTo(otherRank) < 0) {
            return -1; // lower rank
        } else if (rank.compareTo(otherRank) > 0) {
            return 1; // higher rank
        }

        // If ranks are equal, then sort by service id in descending order.
        return (id.compareTo(otherId) < 0) ? 1 : -1;
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof ServiceReference) {
            Object id1 = ((ServiceReference) o).getProperty(Constants.SERVICE_ID);
            Object id2 = this.getProperty(Constants.SERVICE_ID);
            return id1 == id2;
        }
        return m_origin.equals(o);
    }

    @Override
    public int hashCode() {
        return m_origin.hashCode();
    }

    @Override
    public String toString() {
        return getWrappedReference().toString() + m_properties;
    }
}

@@ -5446,16 +5446,53 @@
  return 
+(id1 != null && id1.equals(id2)) %7C%7C (
 id1 == i
@@ -5493,16 +5493,17 @@
 1 == id2
+)
 ;%0A      

Fix FELIX-4132 @Modified not working on Equinox