/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 */
package org.ow2.jonas.lib.security.context;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * Class allowing to marshall/unmarshall Security Context class
 * @author Guillaume Riviere (initial developer)
 * @author Florent Benoit
 */
public class Marshalling {

    /**
     * security context id
     */
    public static final int SEC_CTX_ID = 101;

    /**
     * Utility class
     */
    private Marshalling() {

    }

    /**
     * Custom UTF8 marshalling SecurityContext
     * The resulting bute array is composed of the following elements:
     * principal-name, roles-number, role1, ...., runas-number, runas1, ....
     * then principal stack then roles of principal stack
     * @return byte [] the marshalled context
     * @param ctx SecurityContext
     */
    public static byte[] marshallSecurityContext(SecurityContext ctx) {
        byte [] result = null;
        try {
            if (ctx != null) {
                // build a new byte from ctx
                ArrayList cResult = new ArrayList();
                // add the pname
                addBytes(cResult, ctx.getPrincipalName());
                // add the roles
                String [] roles = ctx.getRoles();
                int nbRoles = 0;
                if (roles != null) {
                    nbRoles = roles.length;
                }
                addBytes(cResult, new Integer(nbRoles).toString());
                for (int i = 0; i < nbRoles; i++) {
                    addBytes(cResult, roles[i]);
                }

                // add runas
                addBytes(cResult, new Integer(ctx.getRunAsRoleStack().size()).toString());
                Iterator iRunAs = ctx.getRunAsRoleStack().iterator();
                while (iRunAs.hasNext()) {
                    addBytes(cResult, (String) iRunAs.next());
                }

                // add runas principal stack
                addBytes(cResult, new Integer(ctx.getRunAsPrincipalStack().size()).toString());
                iRunAs = ctx.getRunAsPrincipalStack().iterator();
                while (iRunAs.hasNext()) {
                    addBytes(cResult, (String) iRunAs.next());
                }

                // add runas roles of principal mapping
                addBytes(cResult, new Integer(ctx.getRunAsPrincipalRolesStack().size()).toString());
                iRunAs = ctx.getRunAsPrincipalRolesStack().iterator();
                while (iRunAs.hasNext()) {
                    addBytes(cResult, (String[]) iRunAs.next());
                }

                // result casting
                result = new byte[cResult.size()];
                for (int i = 0; i < cResult.size(); i++) {
                    result[i] = ((Byte) cResult.get(i)).byteValue();
                }
            }

        } catch (java.io.UnsupportedEncodingException uee) {
            uee.printStackTrace();
        }
        return result;
    }

    /**
     * Custom UTF8 marshalling SecurityContext
     * @param byteCtx the marshalled context
     * @return SecurityContext
     */
    public static SecurityContext unmarshallSecurityContext(byte [] byteCtx) {
        SecurityContext result = null;
        if ((byteCtx != null) && (byteCtx.length > 0)) {
            try {
                Iterator istrs = getBytes2Strings(byteCtx).iterator();
                String principalName = (String) istrs.next();
                int nbRoles = new Integer((String) istrs.next()).intValue();
                ArrayList roles = null;
                if (nbRoles > 0) {
                    roles = new ArrayList();
                }
                while (nbRoles > 0) {
                    roles.add(istrs.next());
                    nbRoles--;
                }

                // unmarshall runAs role
                int nbRunas = new Integer((String) istrs.next()).intValue();
                ArrayList runas = null;
                if (nbRunas > 0) {
                    runas = new ArrayList();
                }
                while (nbRunas > 0) {
                    runas.add(istrs.next());
                    nbRunas--;
                }

                // unmarshall runAs principal
                int nbRunasPrincipal = new Integer((String) istrs.next()).intValue();
                ArrayList runasPrincipal = null;
                if (nbRunasPrincipal > 0) {
                    runasPrincipal = new ArrayList();
                }
                while (nbRunasPrincipal > 0) {
                    runasPrincipal.add(istrs.next());
                    nbRunasPrincipal--;
                }

                // unmarshall runAs principal to roles mapping
                int nbRunasRoles = new Integer((String) istrs.next()).intValue();
                ArrayList runasRoles = null;
                if (nbRunasRoles > 0) {
                    int arrayLength = new Integer((String) istrs.next()).intValue();

                    if (nbRunasRoles > 0) {
                        runasRoles = new ArrayList();
                    }
                    while (nbRunasRoles > 0) {
                        String[] st = new String[arrayLength];
                        for (int j = 0; j < arrayLength; j++) {
                            st[j] = (String) istrs.next();
                        }
                        runasRoles.add(st);
                        nbRunasRoles--;
                    }
                }
                result = new SecurityContext(principalName, roles, runas, runasPrincipal, runasRoles);

            } catch (java.io.UnsupportedEncodingException uee) {
                uee.printStackTrace();
            }
        }
        return result;
    }

    /**
     * Add to the collection of a byte[], the given string.
     * (its lengths, following by its value)
     * @param c collection of byte[]
     * @param toAdd string to transform to a byte[] and to add to the collection
     * @throws java.io.UnsupportedEncodingException if the UTF-8 encoding fails
     */
    private static void addBytes(Collection c, String toAdd) throws java.io.UnsupportedEncodingException {
        byte [] b = toAdd.getBytes("UTF8");
        c.add(new Byte((byte) b.length));
        for (int i = 0; i < b.length; i++) {
            c.add(new Byte(b[i]));
        }
    }

    /**
     * Add to the collection of a byte[], the given string.
     * (its lengths, following by its value)
     * @param c collection of byte[]
     * @param toAdd string to transform to a byte[] and to add to the collection
     * @throws java.io.UnsupportedEncodingException if the UTF-8 encoding fails
     */
    private static void addBytes(Collection c, String[] toAdd) throws java.io.UnsupportedEncodingException {
        // add length of the array
        addBytes(c, new Integer(toAdd.length).toString());
        // add each string of the array
        for (int n = 0; n < toAdd.length; n++) {
            byte [] b = toAdd[n].getBytes("UTF8");
            c.add(new Byte((byte) b.length));
            for (int i = 0; i < b.length; i++) {
                c.add(new Byte(b[i]));
            }
        }
    }

    /**
     * Transform a bytes array to a Strin array.
     * (The bytes array contains for each string, its length, following by its value).
     * @param bytes byte array to transform
     * @return the array list of string
     * @throws java.io.UnsupportedEncodingException if the UTF-8 encoding fails
     */
    private static List getBytes2Strings(byte[] bytes) throws java.io.UnsupportedEncodingException {
        ArrayList strs = new ArrayList();
        int index = 0;
        while (bytes.length > index) {
            int rSize = bytes[index];
            index++;
            byte[] rName = new byte [rSize];
            for (int j = 0; j < rSize; j++) {
                rName[j] = bytes[index];
                index++;
            }
            strs.add(new String(rName, "UTF8"));
        }
        return strs;
    }

}

