1 /*
2  * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package java.util.prefs;
27 
28 import java.util.Objects;
29 
30 class MacOSXPreferences extends AbstractPreferences {
31     // fixme need security checks?
32 
33     // CF preferences file name for Java nodes with short names
34     // This value is also in MacOSXPreferencesFile.c
35     private static final String defaultAppName = "com.apple.java.util.prefs";
36 
37     // true if this node is a child of userRoot or is userRoot
38     private final boolean isUser;
39 
40     // true if this node is userRoot or systemRoot
41     private final boolean isRoot;
42 
43     // CF's storage location for this node and its keys
44     private final MacOSXPreferencesFile file;
45 
46     // absolutePath() + "/"
47     private final String path;
48 
49     // User root and system root nodes
50     private static volatile MacOSXPreferences userRoot;
51     private static volatile MacOSXPreferences systemRoot;
52 
53 
54     // Returns user root node, creating it if necessary.
55     // Called by MacOSXPreferencesFactory
getUserRoot()56     static Preferences getUserRoot() {
57         MacOSXPreferences root = userRoot;
58         if (root == null) {
59             synchronized (MacOSXPreferences.class) {
60                 root = userRoot;
61                 if (root == null) {
62                     userRoot = root = new MacOSXPreferences(true);
63                 }
64             }
65         }
66         return root;
67     }
68 
69 
70     // Returns system root node, creating it if necessary.
71     // Called by MacOSXPreferencesFactory
getSystemRoot()72     static Preferences getSystemRoot() {
73         MacOSXPreferences root = systemRoot;
74         if (root == null) {
75             synchronized (MacOSXPreferences.class) {
76                 root = systemRoot;
77                 if (root == null) {
78                     systemRoot = root = new MacOSXPreferences(false);
79                 }
80             }
81         }
82         return root;
83     }
84 
85 
86     // Create a new root node. Called by getUserRoot() and getSystemRoot()
87     // Synchronization is provided by the caller.
MacOSXPreferences(boolean newIsUser)88     private MacOSXPreferences(boolean newIsUser) {
89         this(null, "", false, true, newIsUser);
90     }
91 
92 
93     // Create a new non-root node with the given parent.
94     // Called by childSpi().
MacOSXPreferences(MacOSXPreferences parent, String name)95     private MacOSXPreferences(MacOSXPreferences parent, String name) {
96         this(parent, name, false, false, false);
97     }
98 
MacOSXPreferences(MacOSXPreferences parent, String name, boolean isNew)99     private MacOSXPreferences(MacOSXPreferences parent, String name,
100                               boolean isNew)
101     {
102         this(parent, name, isNew, false, false);
103     }
104 
MacOSXPreferences(MacOSXPreferences parent, String name, boolean isNew, boolean isRoot, boolean isUser)105     private MacOSXPreferences(MacOSXPreferences parent, String name,
106                               boolean isNew, boolean isRoot, boolean isUser)
107     {
108         super(parent, name);
109         this.isRoot = isRoot;
110         if (isRoot)
111             this.isUser = isUser;
112         else
113             this.isUser = isUserNode();
114         path = isRoot ? absolutePath() : absolutePath() + "/";
115         file = cfFileForNode(this.isUser);
116         if (isNew)
117             newNode = isNew;
118         else
119             newNode = file.addNode(path);
120     }
121 
122     // Create and return the MacOSXPreferencesFile for this node.
123     // Does not write anything to the file.
cfFileForNode(boolean isUser)124     private MacOSXPreferencesFile cfFileForNode(boolean isUser)
125     {
126         String name = path;
127         // /one/two/three/four/five/
128         // The fourth slash is the end of the first three components.
129         // If there is no fourth slash, the name has fewer than 3 components
130         int componentCount = 0;
131         int pos = -1;
132         for (int i = 0; i < 4; i++) {
133             pos = name.indexOf('/', pos+1);
134             if (pos == -1) break;
135         }
136 
137         if (pos == -1) {
138             // fewer than three components - use default name
139             name = defaultAppName;
140         } else {
141             // truncate to three components, no leading or trailing '/'
142             // replace '/' with '.' to make filesystem happy
143             // convert to all lowercase to survive on HFS+
144             name = name.substring(1, pos);
145             name = name.replace('/', '.');
146             name = name.toLowerCase();
147         }
148 
149         return MacOSXPreferencesFile.getFile(name, isUser);
150     }
151 
152 
153     // AbstractPreferences implementation
154     @Override
putSpi(String key, String value)155     protected void putSpi(String key, String value)
156     {
157         file.addKeyToNode(path, key, value);
158     }
159 
160     // AbstractPreferences implementation
161     @Override
getSpi(String key)162     protected String getSpi(String key)
163     {
164         return file.getKeyFromNode(path, key);
165     }
166 
167     // AbstractPreferences implementation
168     @Override
removeSpi(String key)169     protected void removeSpi(String key)
170     {
171         Objects.requireNonNull(key, "Specified key cannot be null");
172         file.removeKeyFromNode(path, key);
173     }
174 
175 
176     // AbstractPreferences implementation
177     @Override
removeNodeSpi()178     protected void removeNodeSpi()
179     throws BackingStoreException
180     {
181         // Disallow flush or sync between these two operations
182         // (they may be manipulating two different files)
183         synchronized(MacOSXPreferencesFile.class) {
184             ((MacOSXPreferences)parent()).removeChild(name());
185             file.removeNode(path);
186         }
187     }
188 
189     // Erase knowledge about a child of this node. Called by removeNodeSpi.
removeChild(String child)190     private void removeChild(String child)
191     {
192         file.removeChildFromNode(path, child);
193     }
194 
195 
196     // AbstractPreferences implementation
197     @Override
childrenNamesSpi()198     protected String[] childrenNamesSpi()
199     throws BackingStoreException
200     {
201         String[] result = file.getChildrenForNode(path);
202         if (result == null) throw new BackingStoreException("Couldn't get list of children for node '" + path + "'");
203         return result;
204     }
205 
206     // AbstractPreferences implementation
207     @Override
keysSpi()208     protected String[] keysSpi()
209     throws BackingStoreException
210     {
211         String[] result = file.getKeysForNode(path);
212         if (result == null) throw new BackingStoreException("Couldn't get list of keys for node '" + path + "'");
213         return result;
214     }
215 
216     // AbstractPreferences implementation
217     @Override
childSpi(String name)218     protected AbstractPreferences childSpi(String name)
219     {
220         // Add to parent's child list here and disallow sync
221         // because parent and child might be in different files.
222         synchronized(MacOSXPreferencesFile.class) {
223             boolean isNew = file.addChildToNode(path, name);
224             return new MacOSXPreferences(this, name, isNew);
225         }
226     }
227 
228     // AbstractPreferences override
229     @Override
flush()230     public void flush()
231     throws BackingStoreException
232     {
233         // Flush should *not* check for removal, unlike sync, but should
234         // prevent simultaneous removal.
235         synchronized(lock) {
236             if (isUser) {
237                 if (!MacOSXPreferencesFile.flushUser()) {
238                     throw new BackingStoreException("Synchronization failed for node '" + path + "'");
239                 }
240             } else {
241                 if (!MacOSXPreferencesFile.flushWorld()) {
242                     throw new BackingStoreException("Synchronization failed for node '" + path + "'");
243                 }
244             }
245         }
246     }
247 
248     // AbstractPreferences implementation
249     @Override
flushSpi()250     protected void flushSpi()
251     throws BackingStoreException
252     {
253         // nothing here - overridden flush() doesn't call this
254     }
255 
256     // AbstractPreferences override
257     @Override
sync()258     public void sync()
259     throws BackingStoreException
260     {
261         synchronized(lock) {
262             if (isRemoved())
263                 throw new IllegalStateException("Node has been removed");
264             // fixme! overkill
265             if (isUser) {
266                 if (!MacOSXPreferencesFile.syncUser()) {
267                     throw new BackingStoreException("Synchronization failed for node '" + path + "'");
268                 }
269             } else {
270                 if (!MacOSXPreferencesFile.syncWorld()) {
271                     throw new BackingStoreException("Synchronization failed for node '" + path + "'");
272                 }
273             }
274         }
275     }
276 
277     // AbstractPreferences implementation
278     @Override
syncSpi()279     protected void syncSpi()
280     throws BackingStoreException
281     {
282         // nothing here - overridden sync() doesn't call this
283     }
284 }
285 
286