Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/io/FilePermission.java
38829 views
/*1* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.io;2627import java.net.URI;28import java.nio.file.InvalidPathException;29import java.security.*;30import java.util.Enumeration;31import java.util.List;32import java.util.ArrayList;33import java.util.Vector;34import java.util.Collections;3536import sun.nio.fs.DefaultFileSystemProvider;37import sun.security.util.SecurityConstants;3839/**40* This class represents access to a file or directory. A FilePermission consists41* of a pathname and a set of actions valid for that pathname.42* <P>43* Pathname is the pathname of the file or directory granted the specified44* actions. A pathname that ends in "/*" (where "/" is45* the file separator character, <code>File.separatorChar</code>) indicates46* all the files and directories contained in that directory. A pathname47* that ends with "/-" indicates (recursively) all files48* and subdirectories contained in that directory. Such a pathname is called49* a wildcard pathname. Otherwise, it's a simple pathname.50* <P>51* A pathname consisting of the special token {@literal "<<ALL FILES>>"}52* matches <b>any</b> file.53* <P>54* Note: A pathname consisting of a single "*" indicates all the files55* in the current directory, while a pathname consisting of a single "-"56* indicates all the files in the current directory and57* (recursively) all files and subdirectories contained in the current58* directory.59* <P>60* The actions to be granted are passed to the constructor in a string containing61* a list of one or more comma-separated keywords. The possible keywords are62* "read", "write", "execute", "delete", and "readlink". Their meaning is63* defined as follows:64*65* <DL>66* <DT> read <DD> read permission67* <DT> write <DD> write permission68* <DT> execute69* <DD> execute permission. Allows <code>Runtime.exec</code> to70* be called. Corresponds to <code>SecurityManager.checkExec</code>.71* <DT> delete72* <DD> delete permission. Allows <code>File.delete</code> to73* be called. Corresponds to <code>SecurityManager.checkDelete</code>.74* <DT> readlink75* <DD> read link permission. Allows the target of a76* <a href="../nio/file/package-summary.html#links">symbolic link</a>77* to be read by invoking the {@link java.nio.file.Files#readSymbolicLink78* readSymbolicLink } method.79* </DL>80* <P>81* The actions string is converted to lowercase before processing.82* <P>83* Be careful when granting FilePermissions. Think about the implications84* of granting read and especially write access to various files and85* directories. The {@literal "<<ALL FILES>>"} permission with write action is86* especially dangerous. This grants permission to write to the entire87* file system. One thing this effectively allows is replacement of the88* system binary, including the JVM runtime environment.89*90* <p>Please note: Code can always read a file from the same91* directory it's in (or a subdirectory of that directory); it does not92* need explicit permission to do so.93*94* @see java.security.Permission95* @see java.security.Permissions96* @see java.security.PermissionCollection97*98*99* @author Marianne Mueller100* @author Roland Schemers101* @since 1.2102*103* @serial exclude104*/105106public final class FilePermission extends Permission implements Serializable {107108/**109* Execute action.110*/111private final static int EXECUTE = 0x1;112/**113* Write action.114*/115private final static int WRITE = 0x2;116/**117* Read action.118*/119private final static int READ = 0x4;120/**121* Delete action.122*/123private final static int DELETE = 0x8;124/**125* Read link action.126*/127private final static int READLINK = 0x10;128129/**130* All actions (read,write,execute,delete,readlink)131*/132private final static int ALL = READ|WRITE|EXECUTE|DELETE|READLINK;133/**134* No actions.135*/136private final static int NONE = 0x0;137138// the actions mask139private transient int mask;140141// does path indicate a directory? (wildcard or recursive)142private transient boolean directory;143144// is it a recursive directory specification?145private transient boolean recursive;146147/**148* the actions string.149*150* @serial151*/152private String actions; // Left null as long as possible, then153// created and re-used in the getAction function.154155// canonicalized dir path. In the case of156// directories, it is the name "/blah/*" or "/blah/-" without157// the last character (the "*" or "-").158159private transient String cpath;160161private transient boolean allFiles; // whether this is <<ALL FILES>>162private transient boolean invalid; // whether input path is invalid163164// static Strings used by init(int mask)165private static final char RECURSIVE_CHAR = '-';166private static final char WILD_CHAR = '*';167168/*169public String toString()170{171StringBuffer sb = new StringBuffer();172sb.append("***\n");173sb.append("cpath = "+cpath+"\n");174sb.append("mask = "+mask+"\n");175sb.append("actions = "+getActions()+"\n");176sb.append("directory = "+directory+"\n");177sb.append("recursive = "+recursive+"\n");178sb.append("***\n");179return sb.toString();180}181*/182183private static final long serialVersionUID = 7930732926638008763L;184185/**186* Always use the internal default file system, in case it was modified187* with java.nio.file.spi.DefaultFileSystemProvider.188*/189private static final java.nio.file.FileSystem builtInFS =190DefaultFileSystemProvider.create()191.getFileSystem(URI.create("file:///"));192193/**194* initialize a FilePermission object. Common to all constructors.195* Also called during de-serialization.196*197* @param mask the actions mask to use.198*199*/200private void init(int mask) {201if ((mask & ALL) != mask)202throw new IllegalArgumentException("invalid actions mask");203204if (mask == NONE)205throw new IllegalArgumentException("invalid actions mask");206207if ((cpath = getName()) == null)208throw new NullPointerException("name can't be null");209210this.mask = mask;211212if (cpath.equals("<<ALL FILES>>")) {213allFiles = true;214directory = true;215recursive = true;216cpath = "";217return;218}219220// Validate path by platform's default file system221// Note: this check does not apply during FilePermission222// class initialization.223if (builtInFS != null) {224try {225String name = cpath.endsWith("*") ?226cpath.substring(0, cpath.length() - 1) + "-" : cpath;227builtInFS.getPath(new File(name).getPath());228} catch (InvalidPathException ipe) {229invalid = true;230return;231}232}233234// store only the canonical cpath if possible235cpath = AccessController.doPrivileged(new PrivilegedAction<String>() {236public String run() {237try {238String path = cpath;239if (cpath.endsWith("*")) {240// call getCanonicalPath with a path with wildcard character241// replaced to avoid calling it with paths that are242// intended to match all entries in a directory243path = path.substring(0, path.length()-1) + "-";244path = new File(path).getCanonicalPath();245return path.substring(0, path.length()-1) + "*";246} else {247return new File(path).getCanonicalPath();248}249} catch (IOException ioe) {250return cpath;251}252}253});254255int len = cpath.length();256char last = ((len > 0) ? cpath.charAt(len - 1) : 0);257258if (last == RECURSIVE_CHAR &&259cpath.charAt(len - 2) == File.separatorChar) {260directory = true;261recursive = true;262cpath = cpath.substring(0, --len);263} else if (last == WILD_CHAR &&264cpath.charAt(len - 2) == File.separatorChar) {265directory = true;266//recursive = false;267cpath = cpath.substring(0, --len);268} else {269// overkill since they are initialized to false, but270// commented out here to remind us...271//directory = false;272//recursive = false;273}274275// XXX: at this point the path should be absolute. die if it isn't?276}277278/**279* Creates a new FilePermission object with the specified actions.280* <i>path</i> is the pathname of a file or directory, and <i>actions</i>281* contains a comma-separated list of the desired actions granted on the282* file or directory. Possible actions are283* "read", "write", "execute", "delete", and "readlink".284*285* <p>A pathname that ends in "/*" (where "/" is286* the file separator character, <code>File.separatorChar</code>)287* indicates all the files and directories contained in that directory.288* A pathname that ends with "/-" indicates (recursively) all files and289* subdirectories contained in that directory. The special pathname290* "<<ALL FILES>>" matches any file.291*292* <p>A pathname consisting of a single "*" indicates all the files293* in the current directory, while a pathname consisting of a single "-"294* indicates all the files in the current directory and295* (recursively) all files and subdirectories contained in the current296* directory.297*298* <p>A pathname containing an empty string represents an empty path.299*300* @param path the pathname of the file/directory.301* @param actions the action string.302*303* @throws IllegalArgumentException304* If actions is <code>null</code>, empty or contains an action305* other than the specified possible actions.306*/307public FilePermission(String path, String actions) {308super(path);309init(getMask(actions));310}311312/**313* Creates a new FilePermission object using an action mask.314* More efficient than the FilePermission(String, String) constructor.315* Can be used from within316* code that needs to create a FilePermission object to pass into the317* <code>implies</code> method.318*319* @param path the pathname of the file/directory.320* @param mask the action mask to use.321*/322323// package private for use by the FilePermissionCollection add method324FilePermission(String path, int mask) {325super(path);326init(mask);327}328329/**330* Checks if this FilePermission object "implies" the specified permission.331* <P>332* More specifically, this method returns true if:333* <ul>334* <li> <i>p</i> is an instanceof FilePermission,335* <li> <i>p</i>'s actions are a proper subset of this336* object's actions, and337* <li> <i>p</i>'s pathname is implied by this object's338* pathname. For example, "/tmp/*" implies "/tmp/foo", since339* "/tmp/*" encompasses all files in the "/tmp" directory,340* including the one named "foo".341* </ul>342* <P>343* Precisely, a simple pathname implies another simple pathname344* if and only if they are equal. A simple pathname never implies345* a wildcard pathname. A wildcard pathname implies another wildcard346* pathname if and only if all simple pathnames implied by the latter347* are implied by the former. A wildcard pathname implies a simple348* pathname if and only if349* <ul>350* <li>if the wildcard flag is "*", the simple pathname's path351* must be right inside the wildcard pathname's path.352* <li>if the wildcard flag is "-", the simple pathname's path353* must be recursively inside the wildcard pathname's path.354* </ul>355* <P>356* {@literal "<<ALL FILES>>"} implies every other pathname. No pathname,357* except for {@literal "<<ALL FILES>>"} itself, implies358* {@literal "<<ALL FILES>>"}.359*360* @param p the permission to check against.361*362* @return <code>true</code> if the specified permission is not363* <code>null</code> and is implied by this object,364* <code>false</code> otherwise.365*/366public boolean implies(Permission p) {367if (!(p instanceof FilePermission))368return false;369370FilePermission that = (FilePermission) p;371372// we get the effective mask. i.e., the "and" of this and that.373// They must be equal to that.mask for implies to return true.374375return ((this.mask & that.mask) == that.mask) && impliesIgnoreMask(that);376}377378/**379* Checks if the Permission's actions are a proper subset of the380* this object's actions. Returns the effective mask iff the381* this FilePermission's path also implies that FilePermission's path.382*383* @param that the FilePermission to check against.384* @return the effective mask385*/386boolean impliesIgnoreMask(FilePermission that) {387if (this == that) {388return true;389}390if (allFiles) {391return true;392}393if (this.invalid || that.invalid) {394return false;395}396if (that.allFiles) {397return false;398}399if (this.directory) {400if (this.recursive) {401// make sure that.path is longer then path so402// something like /foo/- does not imply /foo403if (that.directory) {404return (that.cpath.length() >= this.cpath.length()) &&405that.cpath.startsWith(this.cpath);406} else {407return ((that.cpath.length() > this.cpath.length()) &&408that.cpath.startsWith(this.cpath));409}410} else {411if (that.directory) {412// if the permission passed in is a directory413// specification, make sure that a non-recursive414// permission (i.e., this object) can't imply a recursive415// permission.416if (that.recursive)417return false;418else419return (this.cpath.equals(that.cpath));420} else {421int last = that.cpath.lastIndexOf(File.separatorChar);422if (last == -1)423return false;424else {425// this.cpath.equals(that.cpath.substring(0, last+1));426// Use regionMatches to avoid creating new string427return (this.cpath.length() == (last + 1)) &&428this.cpath.regionMatches(0, that.cpath, 0, last+1);429}430}431}432} else if (that.directory) {433// if this is NOT recursive/wildcarded,434// do not let it imply a recursive/wildcarded permission435return false;436} else {437return (this.cpath.equals(that.cpath));438}439}440441/**442* Checks two FilePermission objects for equality. Checks that <i>obj</i> is443* a FilePermission, and has the same pathname and actions as this object.444*445* @implNote More specifically, two pathnames are the same if and only if446* they have the same wildcard flag and their447* {@code npath} are equal. Or they are both {@literal "<<ALL FILES>>"}.448*449* @param obj the object we are testing for equality with this object.450* @return <code>true</code> if obj is a FilePermission, and has the same451* pathname and actions as this FilePermission object,452* <code>false</code> otherwise.453*/454public boolean equals(Object obj) {455if (obj == this)456return true;457458if (! (obj instanceof FilePermission))459return false;460461FilePermission that = (FilePermission) obj;462463if (this.invalid || that.invalid) {464return false;465}466return (this.mask == that.mask) &&467(this.allFiles == that.allFiles) &&468this.cpath.equals(that.cpath) &&469(this.directory == that.directory) &&470(this.recursive == that.recursive);471}472473/**474* Returns the hash code value for this object.475*476* @return a hash code value for this object.477*/478public int hashCode() {479return 0;480}481482/**483* Converts an actions String to an actions mask.484*485* @param actions the action string.486* @return the actions mask.487*/488private static int getMask(String actions) {489int mask = NONE;490491// Null action valid?492if (actions == null) {493return mask;494}495496// Use object identity comparison against known-interned strings for497// performance benefit (these values are used heavily within the JDK).498if (actions == SecurityConstants.FILE_READ_ACTION) {499return READ;500} else if (actions == SecurityConstants.FILE_WRITE_ACTION) {501return WRITE;502} else if (actions == SecurityConstants.FILE_EXECUTE_ACTION) {503return EXECUTE;504} else if (actions == SecurityConstants.FILE_DELETE_ACTION) {505return DELETE;506} else if (actions == SecurityConstants.FILE_READLINK_ACTION) {507return READLINK;508}509510char[] a = actions.toCharArray();511512int i = a.length - 1;513if (i < 0)514return mask;515516while (i != -1) {517char c;518519// skip whitespace520while ((i!=-1) && ((c = a[i]) == ' ' ||521c == '\r' ||522c == '\n' ||523c == '\f' ||524c == '\t'))525i--;526527// check for the known strings528int matchlen;529530if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') &&531(a[i-2] == 'e' || a[i-2] == 'E') &&532(a[i-1] == 'a' || a[i-1] == 'A') &&533(a[i] == 'd' || a[i] == 'D'))534{535matchlen = 4;536mask |= READ;537538} else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') &&539(a[i-3] == 'r' || a[i-3] == 'R') &&540(a[i-2] == 'i' || a[i-2] == 'I') &&541(a[i-1] == 't' || a[i-1] == 'T') &&542(a[i] == 'e' || a[i] == 'E'))543{544matchlen = 5;545mask |= WRITE;546547} else if (i >= 6 && (a[i-6] == 'e' || a[i-6] == 'E') &&548(a[i-5] == 'x' || a[i-5] == 'X') &&549(a[i-4] == 'e' || a[i-4] == 'E') &&550(a[i-3] == 'c' || a[i-3] == 'C') &&551(a[i-2] == 'u' || a[i-2] == 'U') &&552(a[i-1] == 't' || a[i-1] == 'T') &&553(a[i] == 'e' || a[i] == 'E'))554{555matchlen = 7;556mask |= EXECUTE;557558} else if (i >= 5 && (a[i-5] == 'd' || a[i-5] == 'D') &&559(a[i-4] == 'e' || a[i-4] == 'E') &&560(a[i-3] == 'l' || a[i-3] == 'L') &&561(a[i-2] == 'e' || a[i-2] == 'E') &&562(a[i-1] == 't' || a[i-1] == 'T') &&563(a[i] == 'e' || a[i] == 'E'))564{565matchlen = 6;566mask |= DELETE;567568} else if (i >= 7 && (a[i-7] == 'r' || a[i-7] == 'R') &&569(a[i-6] == 'e' || a[i-6] == 'E') &&570(a[i-5] == 'a' || a[i-5] == 'A') &&571(a[i-4] == 'd' || a[i-4] == 'D') &&572(a[i-3] == 'l' || a[i-3] == 'L') &&573(a[i-2] == 'i' || a[i-2] == 'I') &&574(a[i-1] == 'n' || a[i-1] == 'N') &&575(a[i] == 'k' || a[i] == 'K'))576{577matchlen = 8;578mask |= READLINK;579580} else {581// parse error582throw new IllegalArgumentException(583"invalid permission: " + actions);584}585586// make sure we didn't just match the tail of a word587// like "ackbarfaccept". Also, skip to the comma.588boolean seencomma = false;589while (i >= matchlen && !seencomma) {590switch(a[i-matchlen]) {591case ',':592seencomma = true;593break;594case ' ': case '\r': case '\n':595case '\f': case '\t':596break;597default:598throw new IllegalArgumentException(599"invalid permission: " + actions);600}601i--;602}603604// point i at the location of the comma minus one (or -1).605i -= matchlen;606}607608return mask;609}610611/**612* Return the current action mask. Used by the FilePermissionCollection.613*614* @return the actions mask.615*/616int getMask() {617return mask;618}619620/**621* Return the canonical string representation of the actions.622* Always returns present actions in the following order:623* read, write, execute, delete, readlink.624*625* @return the canonical string representation of the actions.626*/627private static String getActions(int mask) {628StringBuilder sb = new StringBuilder();629boolean comma = false;630631if ((mask & READ) == READ) {632comma = true;633sb.append("read");634}635636if ((mask & WRITE) == WRITE) {637if (comma) sb.append(',');638else comma = true;639sb.append("write");640}641642if ((mask & EXECUTE) == EXECUTE) {643if (comma) sb.append(',');644else comma = true;645sb.append("execute");646}647648if ((mask & DELETE) == DELETE) {649if (comma) sb.append(',');650else comma = true;651sb.append("delete");652}653654if ((mask & READLINK) == READLINK) {655if (comma) sb.append(',');656else comma = true;657sb.append("readlink");658}659660return sb.toString();661}662663/**664* Returns the "canonical string representation" of the actions.665* That is, this method always returns present actions in the following order:666* read, write, execute, delete, readlink. For example, if this FilePermission667* object allows both write and read actions, a call to <code>getActions</code>668* will return the string "read,write".669*670* @return the canonical string representation of the actions.671*/672public String getActions() {673if (actions == null)674actions = getActions(this.mask);675676return actions;677}678679/**680* Returns a new PermissionCollection object for storing FilePermission681* objects.682* <p>683* FilePermission objects must be stored in a manner that allows them684* to be inserted into the collection in any order, but that also enables the685* PermissionCollection <code>implies</code>686* method to be implemented in an efficient (and consistent) manner.687*688* <p>For example, if you have two FilePermissions:689* <OL>690* <LI> <code>"/tmp/-", "read"</code>691* <LI> <code>"/tmp/scratch/foo", "write"</code>692* </OL>693*694* <p>and you are calling the <code>implies</code> method with the FilePermission:695*696* <pre>697* "/tmp/scratch/foo", "read,write",698* </pre>699*700* then the <code>implies</code> function must701* take into account both the "/tmp/-" and "/tmp/scratch/foo"702* permissions, so the effective permission is "read,write",703* and <code>implies</code> returns true. The "implies" semantics for704* FilePermissions are handled properly by the PermissionCollection object705* returned by this <code>newPermissionCollection</code> method.706*707* @return a new PermissionCollection object suitable for storing708* FilePermissions.709*/710public PermissionCollection newPermissionCollection() {711return new FilePermissionCollection();712}713714/**715* WriteObject is called to save the state of the FilePermission716* to a stream. The actions are serialized, and the superclass717* takes care of the name.718*/719private void writeObject(ObjectOutputStream s)720throws IOException721{722// Write out the actions. The superclass takes care of the name723// call getActions to make sure actions field is initialized724if (actions == null)725getActions();726s.defaultWriteObject();727}728729/**730* readObject is called to restore the state of the FilePermission from731* a stream.732*/733private void readObject(ObjectInputStream s)734throws IOException, ClassNotFoundException735{736// Read in the actions, then restore everything else by calling init.737s.defaultReadObject();738init(getMask(actions));739}740}741742/**743* A FilePermissionCollection stores a set of FilePermission permissions.744* FilePermission objects745* must be stored in a manner that allows them to be inserted in any746* order, but enable the implies function to evaluate the implies747* method.748* For example, if you have two FilePermissions:749* <OL>750* <LI> "/tmp/-", "read"751* <LI> "/tmp/scratch/foo", "write"752* </OL>753* And you are calling the implies function with the FilePermission:754* "/tmp/scratch/foo", "read,write", then the implies function must755* take into account both the /tmp/- and /tmp/scratch/foo756* permissions, so the effective permission is "read,write".757*758* @see java.security.Permission759* @see java.security.Permissions760* @see java.security.PermissionCollection761*762*763* @author Marianne Mueller764* @author Roland Schemers765*766* @serial include767*768*/769770final class FilePermissionCollection extends PermissionCollection771implements Serializable772{773// Not serialized; see serialization section at end of class774private transient List<Permission> perms;775776/**777* Create an empty FilePermissionCollection object.778*/779public FilePermissionCollection() {780perms = new ArrayList<>();781}782783/**784* Adds a permission to the FilePermissionCollection. The key for the hash is785* permission.path.786*787* @param permission the Permission object to add.788*789* @exception IllegalArgumentException - if the permission is not a790* FilePermission791*792* @exception SecurityException - if this FilePermissionCollection object793* has been marked readonly794*/795public void add(Permission permission) {796if (! (permission instanceof FilePermission))797throw new IllegalArgumentException("invalid permission: "+798permission);799if (isReadOnly())800throw new SecurityException(801"attempt to add a Permission to a readonly PermissionCollection");802803synchronized (this) {804perms.add(permission);805}806}807808/**809* Check and see if this set of permissions implies the permissions810* expressed in "permission".811*812* @param permission the Permission object to compare813*814* @return true if "permission" is a proper subset of a permission in815* the set, false if not.816*/817public boolean implies(Permission permission) {818if (! (permission instanceof FilePermission))819return false;820821FilePermission fp = (FilePermission) permission;822823int desired = fp.getMask();824int effective = 0;825int needed = desired;826827synchronized (this) {828int len = perms.size();829for (int i = 0; i < len; i++) {830FilePermission x = (FilePermission) perms.get(i);831if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(fp)) {832effective |= x.getMask();833if ((effective & desired) == desired)834return true;835needed = (desired ^ effective);836}837}838}839return false;840}841842/**843* Returns an enumeration of all the FilePermission objects in the844* container.845*846* @return an enumeration of all the FilePermission objects.847*/848public Enumeration<Permission> elements() {849// Convert Iterator into Enumeration850synchronized (this) {851return Collections.enumeration(perms);852}853}854855private static final long serialVersionUID = 2202956749081564585L;856857// Need to maintain serialization interoperability with earlier releases,858// which had the serializable field:859// private Vector permissions;860861/**862* @serialField permissions java.util.Vector863* A list of FilePermission objects.864*/865private static final ObjectStreamField[] serialPersistentFields = {866new ObjectStreamField("permissions", Vector.class),867};868869/**870* @serialData "permissions" field (a Vector containing the FilePermissions).871*/872/*873* Writes the contents of the perms field out as a Vector for874* serialization compatibility with earlier releases.875*/876private void writeObject(ObjectOutputStream out) throws IOException {877// Don't call out.defaultWriteObject()878879// Write out Vector880Vector<Permission> permissions = new Vector<>(perms.size());881synchronized (this) {882permissions.addAll(perms);883}884885ObjectOutputStream.PutField pfields = out.putFields();886pfields.put("permissions", permissions);887out.writeFields();888}889890/*891* Reads in a Vector of FilePermissions and saves them in the perms field.892*/893private void readObject(ObjectInputStream in)894throws IOException, ClassNotFoundException895{896// Don't call defaultReadObject()897898// Read in serialized fields899ObjectInputStream.GetField gfields = in.readFields();900901// Get the one we want902@SuppressWarnings("unchecked")903Vector<Permission> permissions = (Vector<Permission>)gfields.get("permissions", null);904perms = new ArrayList<>(permissions.size());905for (Permission perm : permissions) {906perms.add(perm);907}908}909}910911912