package org.jacorb.poa.util;

/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 1997-2014 Gerald Brose / The JacORB Team.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

import java.util.ArrayList;
import java.util.List;
import org.jacorb.poa.POAConstants;
import org.jacorb.poa.except.POAInternalError;
import org.omg.PortableServer.ID_ASSIGNMENT_POLICY_ID;
import org.omg.PortableServer.ID_UNIQUENESS_POLICY_ID;
import org.omg.PortableServer.IMPLICIT_ACTIVATION_POLICY_ID;
import org.omg.PortableServer.IdAssignmentPolicy;
import org.omg.PortableServer.IdAssignmentPolicyValue;
import org.omg.PortableServer.IdUniquenessPolicy;
import org.omg.PortableServer.IdUniquenessPolicyValue;
import org.omg.PortableServer.ImplicitActivationPolicy;
import org.omg.PortableServer.ImplicitActivationPolicyValue;
import org.omg.PortableServer.LIFESPAN_POLICY_ID;
import org.omg.PortableServer.LifespanPolicy;
import org.omg.PortableServer.LifespanPolicyValue;
import org.omg.PortableServer.REQUEST_PROCESSING_POLICY_ID;
import org.omg.PortableServer.RequestProcessingPolicy;
import org.omg.PortableServer.RequestProcessingPolicyValue;
import org.omg.PortableServer.SERVANT_RETENTION_POLICY_ID;
import org.omg.PortableServer.ServantRetentionPolicy;
import org.omg.PortableServer.ServantRetentionPolicyValue;
import org.omg.PortableServer.THREAD_POLICY_ID;
import org.omg.PortableServer.ThreadPolicy;
import org.omg.PortableServer.ThreadPolicyValue;

/**
 * This class collects some useful routines for the POA.
 *
 * @author Reimo Tiedemann, FU Berlin
 */

public final class POAUtil
{
    static private final int bytesPerLine = 20;
    private static final char[] lookup =
        new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

    private POAUtil () {}

    /**
     * <code>convert</code> outputs a byte oid in a hex string dump formatted
     * like e.g.:
     * 49 6d 52              ImR
     * ....
     *
     * @param data a <code>byte</code> value
     * @return a <code>String</code> value
     */
    public static String convert( byte[] data )
    {
        StringBuffer result = new StringBuffer ();
        int k = 0;

        for (int j = 0; j < data.length; j++)
        {
            result.append( toHex(data[j]));

            boolean lastLine = (j >= (data.length - 1));

            if (lastLine)
            {
                for (int p = 0;
                     p < (bytesPerLine - (j % bytesPerLine) - 1);
                     p++)
                {
                    result.append ("   ");
                }
            }

            if (((j % bytesPerLine) == (bytesPerLine - 1)) || lastLine)
            {
                for (; k <= j; k++)
                {
                    result.append ((data[k] < 32) ? '.' : (char) data[k]);
                }
            }
        }
        return result.toString ();
    }

    /**
     * reads the policy value from the specified policy and
     * converts it into a string
     */

    public static String convert(org.omg.CORBA.Policy policy, int policy_type)
    {
        switch (policy_type)
        {
        case THREAD_POLICY_ID.value:
            if (policy == null || ((ThreadPolicy) policy).value() == ThreadPolicyValue.ORB_CTRL_MODEL) return "ORB_CTRL_MODEL";
            else if (((ThreadPolicy) policy).value() == ThreadPolicyValue.SINGLE_THREAD_MODEL) return "SINGLE_THREAD_MODEL";
            break;

        case LIFESPAN_POLICY_ID.value:
            if (policy == null || ((LifespanPolicy) policy).value() == LifespanPolicyValue.TRANSIENT) return "TRANSIENT";
            else if (((LifespanPolicy) policy).value() == LifespanPolicyValue.PERSISTENT) return "PERSISTENT";
            break;

        case ID_UNIQUENESS_POLICY_ID.value:
            if (policy == null || ((IdUniquenessPolicy) policy).value() == IdUniquenessPolicyValue.UNIQUE_ID) return "UNIQUE_ID";
            else if (((IdUniquenessPolicy) policy).value() == IdUniquenessPolicyValue.MULTIPLE_ID) return "MULTIPLE_ID";
            break;

        case ID_ASSIGNMENT_POLICY_ID.value:
            if (policy == null || ((IdAssignmentPolicy) policy).value() == IdAssignmentPolicyValue.SYSTEM_ID) return "SYSTEM_ID";
            else if (((IdAssignmentPolicy) policy).value() == IdAssignmentPolicyValue.USER_ID) return "USER_ID";
            break;

        case SERVANT_RETENTION_POLICY_ID.value:
            if (policy == null || ((ServantRetentionPolicy) policy).value() == ServantRetentionPolicyValue.RETAIN) return "RETAIN";
            else if (((ServantRetentionPolicy) policy).value() == ServantRetentionPolicyValue.NON_RETAIN) return "NON_RETAIN";
            break;

        case REQUEST_PROCESSING_POLICY_ID.value:
            if (policy == null || ((RequestProcessingPolicy) policy).value() == RequestProcessingPolicyValue.USE_ACTIVE_OBJECT_MAP_ONLY) return "USE_ACTIVE_OBJECT_MAP_ONLY";
            else if (((RequestProcessingPolicy) policy).value() == RequestProcessingPolicyValue.USE_SERVANT_MANAGER) return "USE_SERVANT_MANAGER";
            else if (((RequestProcessingPolicy) policy).value() == RequestProcessingPolicyValue.USE_DEFAULT_SERVANT) return "USE_DEFAULT_SERVANT";
            break;

        case IMPLICIT_ACTIVATION_POLICY_ID.value:
            if (policy == null || ((ImplicitActivationPolicy) policy).value() == ImplicitActivationPolicyValue.NO_IMPLICIT_ACTIVATION) return "NO_IMPLICIT_ACTIVATION";
            else if (((ImplicitActivationPolicy) policy).value() == ImplicitActivationPolicyValue.IMPLICIT_ACTIVATION) return "IMPLICIT_ACTIVATION";
            break;
        }
        return "unknown";
    }

    /**
     * converts the state into a string
     */

    public static String convert(org.omg.PortableServer.POAManagerPackage.State state)
    {
        if (state.value() == org.omg.PortableServer.POAManagerPackage.State._ACTIVE)
            return "active";
        if (state.value() == org.omg.PortableServer.POAManagerPackage.State._HOLDING)
            return "holding";
        if (state.value() == org.omg.PortableServer.POAManagerPackage.State._DISCARDING)
            return "discarding";
        if (state.value() == org.omg.PortableServer.POAManagerPackage.State._INACTIVE)
            return "inactive";

        return "unknown";
    }

    /**
     * extracts the impl name from a specified object key
     */

    public static String extractImplName(byte[] object_key)
    {
        for (int i = 0; i < object_key.length; i++)
        {
            if (object_key[i] == POAConstants.OBJECT_KEY_SEP_BYTE
                && (i==0 || object_key[i-1] != POAConstants.MASK_BYTE))
            {
                return unmaskStr( object_key, 0, i );
            }
        }
        throw new POAInternalError("error extracting impl name from object_key: "+
                                   convert(object_key));
    }


    /**
     * extracts the oid from a specified object key
     */

    public static byte[] extractOID(byte[] object_key)
    {
        for (int i=object_key.length-1; i>=0; i--)
        {
            if (object_key[i] == POAConstants.OBJECT_KEY_SEP_BYTE
                && (i==0 || object_key[i-1] != POAConstants.MASK_BYTE))
            {
                ++i;
                return unmaskId(object_key, i, object_key.length - i);
            }
        }
        throw new POAInternalError("error extracting oid from object_key: "+
                                   convert(object_key));
    }

    /**
     * extracts the oid from a specified object reference
     */

    public static byte[] extractOID(org.omg.CORBA.Object reference)
    {
        return ((org.jacorb.orb.Delegate) ((org.omg.CORBA.portable.ObjectImpl) reference)._get_delegate()).getObjectId();
    }

    /**
     * extracts the poa name from a specified object key
     */
    public static String extractPOAName(byte[] object_key)
    {
        int begin = object_key.length;
        int end = 0;
        for (int i=0; i<object_key.length; i++)
        {
            if (object_key[i] == POAConstants.OBJECT_KEY_SEP_BYTE
                && (i==0 || object_key[i-1] != POAConstants.MASK_BYTE))
            {
                begin = i;
                break;
            }
        }
        for (int i=object_key.length-1; i>=0; i--)
        {
            if (object_key[i] == POAConstants.OBJECT_KEY_SEP_BYTE
                && (i==0 || object_key[i-1] != POAConstants.MASK_BYTE))
            {
                end = i;
                break;
            }
        }
        if (begin > end)
        {
            throw new POAInternalError("error extracting poa name from object_key: "+convert(object_key));
        }
        if (begin == end)
        {
            return "";
        }

        begin++;
        return new String(object_key, begin, end-begin);
    }


    /**
     * <code>extractScopedPOANames</code> returns a list containing the
     * poa_names. This method is faster than using a StringTokenizer.
     *
     * @param poa_name is a <code>String</code> value which may contain
     * poa_names separated by
     * {@link POAConstants#OBJECT_KEY_SEPARATOR OBJECT_KEY_SEPARATOR}
     * @return a <code>Vector</code> value
     */
    public static List<String> extractScopedPOANames (String poa_name)
    {
        final List<String> scopes = new ArrayList<String>();
        final int length = poa_name.length();

        if (length > 0)
        {
            // Fill in the list with the poa_names.
            int previous = 0, current=0;

            for ( ; current < length; ++current)
            {
                // If we've found a separator skip over it and add to the vector
                if (poa_name.charAt (current) == POAConstants.OBJECT_KEY_SEPARATOR)
                {
                    scopes.add (poa_name.substring (previous, current));
                    ++current;
                    previous = current;
                }
            }
            // Add the final POA name
            scopes.add (poa_name.substring (previous, current));
        }
        return scopes;
    }


    /**
     * returns the policy with the specified policy_type from a policy list
     */

    public static org.omg.CORBA.Policy getPolicy(org.omg.CORBA.Policy[] policies, int policy_type)
    {
        if (policies != null)
        {
            for (int i = 0; i < policies.length; i++)
            {
                if (policies[i].policy_type() == policy_type)
                {
                    return policies[i];
                }
            }
        }
        return null;
    }


    public static boolean isActive(org.omg.PortableServer.POAManagerPackage.State state) {
        return state.value() == org.omg.PortableServer.POAManagerPackage.State._ACTIVE ? true : false;
    }


    public static boolean isDiscarding(org.omg.PortableServer.POAManagerPackage.State state) {
        return state.value() == org.omg.PortableServer.POAManagerPackage.State._DISCARDING ? true : false;
    }


    public static boolean isHolding(org.omg.PortableServer.POAManagerPackage.State state) {
        return state.value() == org.omg.PortableServer.POAManagerPackage.State._HOLDING ? true : false;
    }


    public static boolean isInactive(org.omg.PortableServer.POAManagerPackage.State state) {
        return state.value() == org.omg.PortableServer.POAManagerPackage.State._INACTIVE ? true : false;
    }

    /**
     * masks the object key separator bytes
     */

    public static byte[] maskId(byte[] id)
    {
        int altered = id.length;
        for (int i=0; i<id.length; i++)
        {
            if (id[i] == POAConstants.OBJECT_KEY_SEP_BYTE)
            {
                altered++;
            }
            else if (id[i] == POAConstants.MASK_BYTE)
            {
                altered++;
            }
        }
        if (altered == id.length) return id;

        byte[] result = new byte[altered];

        altered = 0;
        for (int i=0; i<id.length; i++) {
            if (id[i] == POAConstants.OBJECT_KEY_SEP_BYTE) {
                result[altered] = POAConstants.MASK_BYTE;
                result[altered+1] = POAConstants.SEPA_MASK_BYTE;
                altered += 2;

            } else if (id[i] == POAConstants.MASK_BYTE) {
                result[altered] = POAConstants.MASK_BYTE;
                result[altered+1] = POAConstants.MASK_MASK_BYTE;
                altered += 2;

            } else {
                result[altered] = id[i];
                altered ++;
            }
        }
        return result;
    }

    /**
     * masks the object key separator chars
     */

    public static String maskStr(String str)
    {
        return new String(maskId(str.getBytes()));
    }

    /**
     * unmasks the object key separator chars
     */

    public static String unmaskStr(String str)
    {
        return new String(unmaskId(str.getBytes()));
    }

    private static String unmaskStr(byte[] data, int offset, int length)
    {
        return new String(unmaskId(data, offset, length));
    }

    /**
     * unmasks the object key separator bytes
     */

    public static byte[] unmaskId(final byte[] id)
    {
        final int altered = getAltered(id, 0, id.length);

        if (altered == id.length)
        {
            return id;
        }
        else
        {
            return unmaskId(id, 0, id.length, altered);
        }
    }

    /**
     * unmasks the object key separator bytes
     */

    private static byte[] unmaskId(final byte[] id, final int start, final int length)
    {
        int altered = getAltered(id, start, length);

        return unmaskId(id, start, length, altered);
    }

    private static int getAltered(final byte[] id, final int start, final int length)
    {
        int altered = length;
        for (int i=start; i<start + length; i++)
        {
            if (id[i] == POAConstants.MASK_BYTE)
            {
                altered--;
                i++;
            }
        }
        return altered;
    }

    private static byte[] unmaskId(final byte[] id, final int start, final int length, final int altered)
    {
        final byte[] result = new byte[altered];

        for (int i=start, resultIdx = 0; i< start + length; ++i, ++resultIdx)
        {
            if (id[i] == POAConstants.MASK_BYTE)
            {
                if (id[i+1] == POAConstants.MASK_MASK_BYTE)
                {
                    result[resultIdx] = POAConstants.MASK_BYTE;
                }
                else if (id[i+1] == POAConstants.SEPA_MASK_BYTE)
                {
                    result[resultIdx] = POAConstants.OBJECT_KEY_SEP_BYTE;
                }
                else
                {
                    throw new POAInternalError("error: forbidden byte sequence \""
                                               +POAConstants.MASK_BYTE+id[i+1]+"\" (unmaskId)");
                }
                ++i;
            }
            else
            {
                result[resultIdx] = id[i];
            }
        }
        return result;
    }


    /**
     * <code>toHex</code> converts a byte into a readable string.
     *
     * @param b a <code>byte</code> value
     * @return a <code>String</code> value
     */

    public static final String toHex(byte b)
    {
        StringBuffer sb = new StringBuffer();

        int upper = (b >> 4) & 0x0F;
        sb.append( lookup[upper] );

        int lower = b & 0x0F;
        sb.append( lookup[lower] );

        sb.append( ' ' );

        return sb.toString();
    }
}
