1 /*
2  * Copyright (c) 2011, 2012, 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     // CF's storage location for this node and its keys
41     private final MacOSXPreferencesFile file;
42 
43     // absolutePath() + "/"
44     private final String path;
45 
46     // User root and system root nodes
47     private static volatile MacOSXPreferences userRoot;
48     private static volatile MacOSXPreferences systemRoot;
49 
50 
51     // Returns user root node, creating it if necessary.
52     // Called by MacOSXPreferencesFactory
getUserRoot()53     static Preferences getUserRoot() {
54         MacOSXPreferences root = userRoot;
55         if (root == null) {
56             synchronized (MacOSXPreferences.class) {
57                 root = userRoot;
58                 if (root == null) {
59                     userRoot = root = new MacOSXPreferences(true);
60                 }
61             }
62         }
63         return root;
64     }
65 
66 
67     // Returns system root node, creating it if necessary.
68     // Called by MacOSXPreferencesFactory
getSystemRoot()69     static Preferences getSystemRoot() {
70         MacOSXPreferences root = systemRoot;
71         if (root == null) {
72             synchronized (MacOSXPreferences.class) {
73                 root = systemRoot;
74                 if (root == null) {
75                     systemRoot = root = new MacOSXPreferences(false);
76                 }
77             }
78         }
79         return root;
80     }
81 
82 
83     // Create a new root node. Called by getUserRoot() and getSystemRoot()
84     // Synchronization is provided by the caller.
MacOSXPreferences(boolean newIsUser)85     private MacOSXPreferences(boolean newIsUser) {
86         this(null, "", false, true, newIsUser);
87     }
88 
89 
90     // Create a new non-root node with the given parent.
91     // Called by childSpi().
MacOSXPreferences(MacOSXPreferences parent, String name)92     private MacOSXPreferences(MacOSXPreferences parent, String name) {
93         this(parent, name, false, false, false);
94     }
95 
MacOSXPreferences(MacOSXPreferences parent, String name, boolean isNew)96     private MacOSXPreferences(MacOSXPreferences parent, String name,
97                               boolean isNew)
98     {
99         this(parent, name, isNew, false, false);
100     }
101 
MacOSXPreferences(MacOSXPreferences parent, String name, boolean isNew, boolean isRoot, boolean isUser)102     private MacOSXPreferences(MacOSXPreferences parent, String name,
103                               boolean isNew, boolean isRoot, boolean isUser)
104     {
105         super(parent, name);
106         if (isRoot)
107             this.isUser = isUser;
108         else
109             this.isUser = isUserNode();
110         path = isRoot ? absolutePath() : absolutePath() + "/";
111         file = cfFileForNode(this.isUser);
112         if (isNew)
113             newNode = isNew;
114         else
115             newNode = file.addNode(path);
116     }
117 
118     // Create and return the MacOSXPreferencesFile for this node.
119     // Does not write anything to the file.
cfFileForNode(boolean isUser)120     private MacOSXPreferencesFile cfFileForNode(boolean isUser)
121     {
122         String name = path;
123         // /one/two/three/four/five/
124         // The fourth slash is the end of the first three components.
125         // If there is no fourth slash, the name has fewer than 3 components
126         int pos = -1;
127         for (int i = 0; i < 4; i++) {
128             pos = name.indexOf('/', pos+1);
129             if (pos == -1) break;
130         }
131 
132         if (pos == -1) {
133             // fewer than three components - use default name
134             name = defaultAppName;
135         } else {
136             // truncate to three components, no leading or trailing '/'
137             // replace '/' with '.' to make filesystem happy
138             // convert to all lowercase to survive on HFS+
139             name = name.substring(1, pos);
140             name = name.replace('/', '.');
141             name = name.toLowerCase();
142         }
143 
144         return MacOSXPreferencesFile.getFile(name, isUser);
145     }
146 
147 
148     // AbstractPreferences implementation
149     @Override
putSpi(String key, String value)150     protected void putSpi(String key, String value)
151     {
152         file.addKeyToNode(path, key, value);
153     }
154 
155     // AbstractPreferences implementation
156     @Override
getSpi(String key)157     protected String getSpi(String key)
158     {
159         return file.getKeyFromNode(path, key);
160     }
161 
162     // AbstractPreferences implementation
163     @Override
removeSpi(String key)164     protected void removeSpi(String key)
165     {
166         Objects.requireNonNull(key, "Specified key cannot be null");
167         file.removeKeyFromNode(path, key);
168     }
169 
170 
171     // AbstractPreferences implementation
172     @Override
removeNodeSpi()173     protected void removeNodeSpi()
174     throws BackingStoreException
175     {
176         // Disallow flush or sync between these two operations
177         // (they may be manipulating two different files)
178         synchronized(MacOSXPreferencesFile.class) {
179             ((MacOSXPreferences)parent()).removeChild(name());
180             file.removeNode(path);
181         }
182     }
183 
184     // Erase knowledge about a child of this node. Called by removeNodeSpi.
removeChild(String child)185     private void removeChild(String child)
186     {
187         file.removeChildFromNode(path, child);
188     }
189 
190 
191     // AbstractPreferences implementation
192     @Override
childrenNamesSpi()193     protected String[] childrenNamesSpi()
194     throws BackingStoreException
195     {
196         String[] result = file.getChildrenForNode(path);
197         if (result == null) throw new BackingStoreException("Couldn't get list of children for node '" + path + "'");
198         return result;
199     }
200 
201     // AbstractPreferences implementation
202     @Override
keysSpi()203     protected String[] keysSpi()
204     throws BackingStoreException
205     {
206         String[] result = file.getKeysForNode(path);
207         if (result == null) throw new BackingStoreException("Couldn't get list of keys for node '" + path + "'");
208         return result;
209     }
210 
211     // AbstractPreferences implementation
212     @Override
childSpi(String name)213     protected AbstractPreferences childSpi(String name)
214     {
215         // Add to parent's child list here and disallow sync
216         // because parent and child might be in different files.
217         synchronized(MacOSXPreferencesFile.class) {
218             boolean isNew = file.addChildToNode(path, name);
219             return new MacOSXPreferences(this, name, isNew);
220         }
221     }
222 
223     // AbstractPreferences override
224     @Override
flush()225     public void flush()
226     throws BackingStoreException
227     {
228         // Flush should *not* check for removal, unlike sync, but should
229         // prevent simultaneous removal.
230         synchronized(lock) {
231             if (isUser) {
232                 if (!MacOSXPreferencesFile.flushUser()) {
233                     throw new BackingStoreException("Synchronization failed for node '" + path + "'");
234                 }
235             } else {
236                 if (!MacOSXPreferencesFile.flushWorld()) {
237                     throw new BackingStoreException("Synchronization failed for node '" + path + "'");
238                 }
239             }
240         }
241     }
242 
243     // AbstractPreferences implementation
244     @Override
flushSpi()245     protected void flushSpi()
246     throws BackingStoreException
247     {
248         // nothing here - overridden flush() doesn't call this
249     }
250 
251     // AbstractPreferences override
252     @Override
sync()253     public void sync()
254     throws BackingStoreException
255     {
256         synchronized(lock) {
257             if (isRemoved())
258                 throw new IllegalStateException("Node has been removed");
259             // fixme! overkill
260             if (isUser) {
261                 if (!MacOSXPreferencesFile.syncUser()) {
262                     throw new BackingStoreException("Synchronization failed for node '" + path + "'");
263                 }
264             } else {
265                 if (!MacOSXPreferencesFile.syncWorld()) {
266                     throw new BackingStoreException("Synchronization failed for node '" + path + "'");
267                 }
268             }
269         }
270     }
271 
272     // AbstractPreferences implementation
273     @Override
syncSpi()274     protected void syncSpi()
275     throws BackingStoreException
276     {
277         // nothing here - overridden sync() doesn't call this
278     }
279 }
280 
281