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.util.Collections;
26import java.util.Enumeration;
27import java.util.HashMap;
28import java.util.Hashtable;
29import java.util.Iterator;
30import java.util.Map;
31
32import org.apache.harmony.security.internal.nls.Messages;
33
34/**
35 * Specific {@code PermissionCollection} for storing {@code BasicPermissions} of
36 * arbitrary type.
37 *
38 * @see BasicPermission
39 * @see PermissionCollection
40 */
41final class BasicPermissionCollection extends PermissionCollection {
42
43    private static final long serialVersionUID = 739301742472979399L;
44
45    private static final ObjectStreamField[] serialPersistentFields = {
46        new ObjectStreamField("all_allowed", Boolean.TYPE), //$NON-NLS-1$
47        new ObjectStreamField("permissions", Hashtable.class), //$NON-NLS-1$
48        new ObjectStreamField("permClass", Class.class), }; //$NON-NLS-1$
49
50    //should be final, but because of writeObject() cannot be
51    private transient Map<String, Permission> items = new HashMap<String, Permission>();
52
53    // true if this Collection contains a BasicPermission with '*' as its permission name
54    private transient boolean allEnabled; // = false;
55
56    private Class<? extends Permission> permClass;
57
58    /**
59     * Adds a permission to the collection. The first added permission must be a
60     * subclass of BasicPermission, next permissions must be of the same class
61     * as the first one.
62     *
63     * @see java.security.PermissionCollection#add(java.security.Permission)
64     */
65    @Override
66    public void add(Permission permission) {
67        if (isReadOnly()) {
68            throw new SecurityException(Messages.getString("security.15")); //$NON-NLS-1$
69        }
70        if (permission == null) {
71            throw new IllegalArgumentException(Messages.getString("security.20")); //$NON-NLS-1$
72        }
73
74        Class<? extends Permission> inClass = permission.getClass();
75        if (permClass != null) {
76            if (permClass != inClass) {
77                throw new IllegalArgumentException(Messages.getString("security.16", //$NON-NLS-1$
78                    permission));
79            }
80        } else if( !(permission instanceof BasicPermission)) {
81            throw new IllegalArgumentException(Messages.getString("security.16", //$NON-NLS-1$
82                permission));
83        } else {
84            // this is the first element provided that another thread did not add
85            synchronized (this) {
86                if (permClass != null && inClass != permClass) {
87                    throw new IllegalArgumentException(Messages.getString("security.16", //$NON-NLS-1$
88                        permission));
89                }
90                permClass = inClass;
91            }
92        }
93
94        String name = permission.getName();
95        items.put(name, permission);
96        allEnabled = allEnabled || (name.length() == 1 && '*' == name.charAt(0));
97    }
98
99    /**
100     * Returns enumeration of contained elements.
101     */
102    @Override
103    public Enumeration<Permission> elements() {
104        return Collections.enumeration(items.values());
105    }
106
107    /**
108     * Indicates whether the argument permission is implied by the receiver.
109     *
110     * @return boolean {@code true} if the argument permission is implied by the
111     *         receiver, and {@code false} if it is not.
112     * @param permission
113     *            the permission to check.
114     * @see Permission
115     */
116    @Override
117    public boolean implies(Permission permission) {
118        if (permission == null || permission.getClass() != permClass) {
119            return false;
120        }
121        if (allEnabled) {
122            return true;
123        }
124        String checkName = permission.getName();
125        //first check direct coincidence
126        if (items.containsKey(checkName)) {
127            return true;
128        }
129        //now check if there are suitable wildcards
130        //suppose we have "a.b.c", let's check "a.b.*" and "a.*"
131        char[] name = checkName.toCharArray();
132        //I presume that "a.b.*" does not imply "a.b."
133        //so the dot at end is ignored
134        int pos = name.length - 2;
135        for (; pos >= 0; pos--) {
136            if (name[pos] == '.') {
137                break;
138            }
139        }
140        while (pos >= 0) {
141            name[pos + 1] = '*';
142            if (items.containsKey(new String(name, 0, pos + 2))) {
143                return true;
144            }
145            for (--pos; pos >= 0; pos--) {
146                if (name[pos] == '.') {
147                    break;
148                }
149            }
150        }
151        return false;
152    }
153
154    /**
155     * Expected format is the following:
156     * <dl>
157     * <dt>boolean all_allowed
158     * <dd>This is set to true if this BasicPermissionCollection contains a
159     * {@code BasicPermission} with '*' as its permission name.
160     * <dt>Class&lt;T&gt; permClass
161     * <dd>The class to which all {@code BasicPermission}s in this
162     * BasicPermissionCollection belongs.
163     * <dt>Hashtable&lt;K,V&gt; permissions
164     * <dd>The {@code BasicPermission}s in this collection. All {@code
165     * BasicPermission}s in the collection must belong to the same class. The
166     * Hashtable is indexed by the {@code BasicPermission} name; the value of
167     * the Hashtable entry is the permission.
168     * </dl>
169     */
170    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
171        ObjectOutputStream.PutField fields = out.putFields();
172        fields.put("all_allowed", allEnabled); //$NON-NLS-1$
173        fields.put("permissions", new Hashtable<String, Permission>(items)); //$NON-NLS-1$
174        fields.put("permClass", permClass); //$NON-NLS-1$
175        out.writeFields();
176    }
177
178    /**
179     * Reads the object from stream and checks its consistency: all contained
180     * permissions must be of the same subclass of BasicPermission.
181     */
182    private void readObject(java.io.ObjectInputStream in) throws IOException,
183        ClassNotFoundException {
184        ObjectInputStream.GetField fields = in.readFields();
185
186        items = new HashMap<String, Permission>();
187        synchronized (this) {
188            permClass = (Class<? extends Permission>)fields.get("permClass", null); //$NON-NLS-1$
189            items.putAll((Hashtable<String, Permission>) fields.get(
190                    "permissions", new Hashtable<String, Permission>())); //$NON-NLS-1$
191            for (Iterator<Permission> iter = items.values().iterator(); iter.hasNext();) {
192                if (iter.next().getClass() != permClass) {
193                    throw new InvalidObjectException(Messages.getString("security.24")); //$NON-NLS-1$
194                }
195            }
196            allEnabled = fields.get("all_allowed", false); //$NON-NLS-1$
197            if (allEnabled && !items.containsKey("*")) { //$NON-NLS-1$
198                throw new InvalidObjectException(Messages.getString("security.25")); //$NON-NLS-1$
199            }
200        }
201    }
202}
203