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

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

/**
 * {@code UnresolvedPermissionCollection} represents a specific {@code
 * PermissionCollection} for storing {@link UnresolvedPermission} instances.
 * Contained elements are grouped by their target type.
 */
final class UnresolvedPermissionCollection extends PermissionCollection {

    private static final long serialVersionUID = -7176153071733132400L;

    private static final ObjectStreamField[] serialPersistentFields = {
        new ObjectStreamField("permissions", Hashtable.class), };

    // elements of the collection.
    private transient Map klasses = new HashMap();

    /**
     * Adds an unresolved permission to this {@code
     * UnresolvedPermissionCollection}.
     *
     * @param permission
     *            the permission to be added.
     * @throws SecurityException
     *             if this collection is read only.
     * @throws IllegalArgumentException
     *             if {@code permission} is {@code null} or not an {@code
     *             UnresolvedPermission}.
     */
    public void add(Permission permission) {
        if (isReadOnly()) {
            throw new SecurityException("collection is read-only");
        }
        if (permission == null || permission.getClass() != UnresolvedPermission.class) {
            throw new IllegalArgumentException("Invalid permission: " + permission);
        }
        synchronized (klasses) {
            String klass = permission.getName();
            Collection klassMates = (Collection)klasses.get(klass);
            if (klassMates == null) {
                klassMates = new HashSet();
                klasses.put(klass, klassMates);
            }
            klassMates.add(permission);
        }
    }

    public Enumeration elements() {
        Collection all = new ArrayList();
        for (Iterator iter = klasses.values().iterator(); iter.hasNext();) {
            all.addAll((Collection)iter.next());
        }
        return Collections.enumeration(all);
    }

    /**
     * Always returns {@code false}.
     *
     * @return always {@code false}
     * @see UnresolvedPermission#implies(Permission).
     */
    public boolean implies(Permission permission) {
        return false;
    }

    /**
     * Returns true if this collection contains unresolved permissions
     * with the same classname as argument permission.
     */
    boolean hasUnresolved(Permission permission) {
        return klasses.containsKey(permission.getClass().getName());
    }

    /**
     * Resolves all permissions of the same class as the specified target
     * permission and adds them to the specified collection. If passed
     * collection is {@code null} and some unresolved permissions were resolved,
     * an appropriate new collection is instantiated and used. All resolved
     * permissions are removed from this unresolved collection, and collection
     * with resolved ones is returned.
     *
     * @param target
     *            a kind of permissions to be resolved.
     * @param holder
     *            an existing collection for storing resolved permissions.
     * @return a collection containing resolved permissions (if any found)
     */
    PermissionCollection resolveCollection(Permission target,
                                           PermissionCollection holder) {
        String klass = target.getClass().getName();
        if (klasses.containsKey(klass)) {
            synchronized (klasses) {
                Collection klassMates = (Collection)klasses.get(klass);
                for (Iterator iter = klassMates.iterator(); iter.hasNext();) {
                    UnresolvedPermission element = (UnresolvedPermission)iter
                        .next();
                    Permission resolved = element.resolve(target.getClass());
                    if (resolved != null) {
                        if (holder == null) {
                            holder = target.newPermissionCollection();
                            if (holder == null) {
                                holder = new PermissionsHash();
                            }
                        }
                        holder.add(resolved);
                        iter.remove();
                    }
                }
                if (klassMates.size() == 0) {
                    klasses.remove(klass);
                }
            }
        }
        return holder;
    }

    /**
     * Output fields via default mechanism.
     */
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        Hashtable permissions = new Hashtable();
        for (Iterator iter = klasses.entrySet().iterator(); iter.hasNext();) {
            Map.Entry entry = (Map.Entry) iter.next();
            String key = (String) entry.getKey();
            permissions.put(key, new Vector(((Collection) entry.getValue())));
        }
        ObjectOutputStream.PutField fields = out.putFields();
        fields.put("permissions", permissions);
        out.writeFields();
    }

    /**
     * Reads the object from stream and checks elements grouping for validity.
     */
    private void readObject(java.io.ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        ObjectInputStream.GetField fields = in.readFields();
        Map permissions = (Map)fields.get("permissions", null);
        klasses = new HashMap();
        synchronized (klasses) {
            for (Iterator iter = permissions.entrySet().iterator(); iter
                .hasNext();) {
                Map.Entry entry = (Map.Entry) iter.next();
                String key = (String) entry.getKey();
                Collection values = (Collection) entry.getValue();

                for (Iterator iterator = values.iterator(); iterator.hasNext();) {
                    UnresolvedPermission element =
                            (UnresolvedPermission) iterator.next();

                    if (!element.getName().equals(key)) {
                        throw new InvalidObjectException("collection is corrupted");
                    }
                }
                klasses.put(key, new HashSet(values));
            }
        }
    }
}
