1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.security;
19
20import java.io.IOException;
21import java.io.InvalidObjectException;
22import java.io.ObjectInputStream;
23import java.io.ObjectOutputStream;
24import java.io.ObjectStreamField;
25import java.io.Serializable;
26import java.util.Enumeration;
27import java.util.HashMap;
28import java.util.Hashtable;
29import java.util.Iterator;
30import java.util.Map;
31import java.util.NoSuchElementException;
32
33import org.apache.harmony.security.internal.nls.Messages;
34
35/**
36 * {@code Permissions} represents a {@code PermissionCollection} where the
37 * contained permissions can be of different types. The permissions are
38 * organized in their appropriate {@code PermissionCollection} obtained by
39 * {@link Permission#newPermissionCollection()}. For permissions which do not
40 * provide a dedicated {@code PermissionCollection}, a default permission
41 * collection, based on a hash table, will be used.
42 */
43public final class Permissions extends PermissionCollection implements
44    Serializable {
45
46    private static final long serialVersionUID = 4858622370623524688L;
47
48    private static final ObjectStreamField[] serialPersistentFields = {
49        new ObjectStreamField("perms", Hashtable.class), //$NON-NLS-1$
50        new ObjectStreamField("allPermission", PermissionCollection.class), }; //$NON-NLS-1$
51
52    // Hash to store PermissionCollection's
53    private transient Map klasses = new HashMap();
54
55    private boolean allEnabled;  // = false;
56
57    /**
58     * Adds the given {@code Permission} to this heterogeneous {@code
59     * PermissionCollection}. The {@code permission} is stored in its
60     * appropriate {@code PermissionCollection}.
61     *
62     * @param permission
63     *            the {@code Permission} to be added.
64     * @throws SecurityException
65     *             if this collection's {@link #isReadOnly()} method returns
66     *             {@code true}.
67     * @throws NullPointerException
68     *             if {@code permission} is {@code null}.
69     */
70    public void add(Permission permission) {
71        if (isReadOnly()) {
72            throw new SecurityException(Messages.getString("security.15")); //$NON-NLS-1$
73        }
74
75        if (permission == null) {
76            throw new NullPointerException(Messages.getString("security.20")); //$NON-NLS-1$
77        }
78
79        Class klass = permission.getClass();
80        PermissionCollection klassMates = (PermissionCollection)klasses
81            .get(klass);
82
83        if (klassMates == null) {
84            synchronized (klasses) {
85                klassMates = (PermissionCollection)klasses.get(klass);
86                if (klassMates == null) {
87
88                    klassMates = permission.newPermissionCollection();
89                    if (klassMates == null) {
90                        klassMates = new PermissionsHash();
91                    }
92                    klasses.put(klass, klassMates);
93                }
94            }
95        }
96        klassMates.add(permission);
97
98        if (klass == AllPermission.class) {
99            allEnabled = true;
100        }
101    }
102
103    public Enumeration<Permission> elements() {
104        return new MetaEnumeration(klasses.values().iterator());
105    }
106
107    /**
108     * An auxiliary implementation for enumerating individual permissions from a
109     * collection of PermissionCollections.
110     *
111     */
112    final static class MetaEnumeration implements Enumeration {
113
114        private Iterator pcIter;
115
116        private Enumeration current;
117
118        /**
119         * Initiates this enumeration.
120         *
121         * @param outer an iterator over external collection of
122         *        PermissionCollections
123         */
124        public MetaEnumeration(Iterator outer) {
125            pcIter = outer;
126            current = getNextEnumeration();
127        }
128
129        private Enumeration getNextEnumeration() {
130            while (pcIter.hasNext()) {
131                Enumeration en = ((PermissionCollection)pcIter.next())
132                    .elements();
133                if (en.hasMoreElements()) {
134                    return en;
135                }
136            }
137            return null;
138        }
139
140        /**
141         * Indicates if there are more elements to enumerate.
142         */
143        public boolean hasMoreElements() {
144            return current != null /* && current.hasMoreElements() */;
145        }
146
147        /**
148         * Returns next element.
149         */
150        public Object nextElement() {
151            if (current != null) {
152                //assert current.hasMoreElements();
153                Object next = current.nextElement();
154                if (!current.hasMoreElements()) {
155                    current = getNextEnumeration();
156                }
157
158                return next;
159            }
160            throw new NoSuchElementException(Messages.getString("security.17")); //$NON-NLS-1$
161        }
162    }
163
164    public boolean implies(Permission permission) {
165        if (permission == null) {
166            // RI compatible
167            throw new NullPointerException(Messages.getString("security.21")); //$NON-NLS-1$
168        }
169        if (allEnabled) {
170            return true;
171        }
172        Class klass = permission.getClass();
173        PermissionCollection klassMates = null;
174
175        UnresolvedPermissionCollection billets = (UnresolvedPermissionCollection)klasses
176            .get(UnresolvedPermission.class);
177        if (billets != null && billets.hasUnresolved(permission)) {
178            // try to fill up klassMates with freshly resolved permissions
179            synchronized (klasses) {
180                klassMates = (PermissionCollection)klasses.get(klass);
181                try {
182                    klassMates = billets.resolveCollection(permission,
183                                                           klassMates);
184                } catch (Exception ignore) {
185                    //TODO log warning
186                    ignore.printStackTrace();
187                }
188
189                if (klassMates != null) {
190                    //maybe klassMates were just created
191                    // so put them into common map
192                    klasses.put(klass, klassMates);
193                    // very uncommon case, but not improbable one
194                    if (klass == AllPermission.class) {
195                        allEnabled = true;
196                    }
197                }
198            }
199        } else {
200            klassMates = (PermissionCollection)klasses.get(klass);
201        }
202
203        if (klassMates != null) {
204            return klassMates.implies(permission);
205        }
206        return false;
207    }
208
209    /**
210     * Reads the object from stream and checks for consistency.
211     */
212    private void readObject(java.io.ObjectInputStream in) throws IOException,
213        ClassNotFoundException {
214        ObjectInputStream.GetField fields = in.readFields();
215        Map perms = (Map)fields.get("perms", null); //$NON-NLS-1$
216        klasses = new HashMap();
217        synchronized (klasses) {
218            for (Iterator iter = perms.entrySet().iterator(); iter.hasNext();) {
219                Map.Entry entry = (Map.Entry)  iter.next();
220                Class key = (Class) entry.getKey();
221                PermissionCollection pc = (PermissionCollection) entry.getValue();
222                if (key != pc.elements().nextElement().getClass()) {
223                    throw new InvalidObjectException(Messages.getString("security.22")); //$NON-NLS-1$
224                }
225                klasses.put(key, pc);
226            }
227        }
228        allEnabled = fields.get("allPermission", null) != null; //$NON-NLS-1$
229        if (allEnabled && !klasses.containsKey(AllPermission.class)) {
230            throw new InvalidObjectException(Messages.getString("security.23")); //$NON-NLS-1$
231        }
232    }
233
234    /**
235     * Outputs fields via default mechanism.
236     */
237    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
238        ObjectOutputStream.PutField fields = out.putFields();
239        fields.put("perms", new Hashtable(klasses)); //$NON-NLS-1$
240        fields.put("allPermission", allEnabled ? klasses //$NON-NLS-1$
241            .get(AllPermission.class) : null);
242        out.writeFields();
243    }
244}
245