1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.base.test.util;
6
7import android.content.SharedPreferences;
8
9import java.util.Collections;
10import java.util.HashMap;
11import java.util.Map;
12import java.util.Set;
13
14/**
15 * An implementation of SharedPreferences that can be used in tests.
16 * <p/>
17 * It keeps all state in memory, and there is no difference between apply() and commit().
18 */
19public class InMemorySharedPreferences implements SharedPreferences {
20
21    // Guarded on its own monitor.
22    private final Map<String, Object> mData;
23
24    public InMemorySharedPreferences() {
25        mData = new HashMap<String, Object>();
26    }
27
28    public InMemorySharedPreferences(Map<String, Object> data) {
29        mData = data;
30    }
31
32    @Override
33    public Map<String, ?> getAll() {
34        synchronized (mData) {
35            return Collections.unmodifiableMap(mData);
36        }
37    }
38
39    @Override
40    public String getString(String key, String defValue) {
41        synchronized (mData) {
42            if (mData.containsKey(key)) {
43                return (String) mData.get(key);
44            }
45        }
46        return defValue;
47    }
48
49    @SuppressWarnings("unchecked")
50    @Override
51    public Set<String> getStringSet(String key, Set<String> defValues) {
52        synchronized (mData) {
53            if (mData.containsKey(key)) {
54                return Collections.unmodifiableSet((Set<String>) mData.get(key));
55            }
56        }
57        return defValues;
58    }
59
60    @Override
61    public int getInt(String key, int defValue) {
62        synchronized (mData) {
63            if (mData.containsKey(key)) {
64                return (Integer) mData.get(key);
65            }
66        }
67        return defValue;
68    }
69
70    @Override
71    public long getLong(String key, long defValue) {
72        synchronized (mData) {
73            if (mData.containsKey(key)) {
74                return (Long) mData.get(key);
75            }
76        }
77        return defValue;
78    }
79
80    @Override
81    public float getFloat(String key, float defValue) {
82        synchronized (mData) {
83            if (mData.containsKey(key)) {
84                return (Float) mData.get(key);
85            }
86        }
87        return defValue;
88    }
89
90    @Override
91    public boolean getBoolean(String key, boolean defValue) {
92        synchronized (mData) {
93            if (mData.containsKey(key)) {
94                return (Boolean) mData.get(key);
95            }
96        }
97        return defValue;
98    }
99
100    @Override
101    public boolean contains(String key) {
102        synchronized (mData) {
103            return mData.containsKey(key);
104        }
105    }
106
107    @Override
108    public SharedPreferences.Editor edit() {
109        return new InMemoryEditor();
110    }
111
112    @Override
113    public void registerOnSharedPreferenceChangeListener(
114            SharedPreferences.OnSharedPreferenceChangeListener
115                    listener) {
116        throw new UnsupportedOperationException();
117    }
118
119    @Override
120    public void unregisterOnSharedPreferenceChangeListener(
121            SharedPreferences.OnSharedPreferenceChangeListener listener) {
122        throw new UnsupportedOperationException();
123    }
124
125    private class InMemoryEditor implements SharedPreferences.Editor {
126
127        // All guarded by |mChanges|
128        private boolean mClearCalled;
129        private volatile boolean mApplyCalled;
130        private final Map<String, Object> mChanges = new HashMap<String, Object>();
131
132        @Override
133        public SharedPreferences.Editor putString(String key, String value) {
134            synchronized (mChanges) {
135                if (mApplyCalled) throw new IllegalStateException();
136                mChanges.put(key, value);
137                return this;
138            }
139        }
140
141        @Override
142        public SharedPreferences.Editor putStringSet(String key, Set<String> values) {
143            synchronized (mChanges) {
144                if (mApplyCalled) throw new IllegalStateException();
145                mChanges.put(key, values);
146                return this;
147            }
148        }
149
150        @Override
151        public SharedPreferences.Editor putInt(String key, int value) {
152            synchronized (mChanges) {
153                if (mApplyCalled) throw new IllegalStateException();
154                mChanges.put(key, value);
155                return this;
156            }
157        }
158
159        @Override
160        public SharedPreferences.Editor putLong(String key, long value) {
161            synchronized (mChanges) {
162                if (mApplyCalled) throw new IllegalStateException();
163                mChanges.put(key, value);
164                return this;
165            }
166        }
167
168        @Override
169        public SharedPreferences.Editor putFloat(String key, float value) {
170            synchronized (mChanges) {
171                if (mApplyCalled) throw new IllegalStateException();
172                mChanges.put(key, value);
173                return this;
174            }
175        }
176
177        @Override
178        public SharedPreferences.Editor putBoolean(String key, boolean value) {
179            synchronized (mChanges) {
180                if (mApplyCalled) throw new IllegalStateException();
181                mChanges.put(key, value);
182                return this;
183            }
184        }
185
186        @Override
187        public SharedPreferences.Editor remove(String key) {
188            synchronized (mChanges) {
189                if (mApplyCalled) throw new IllegalStateException();
190                // Magic value for removes
191                mChanges.put(key, this);
192                return this;
193            }
194        }
195
196        @Override
197        public SharedPreferences.Editor clear() {
198            synchronized (mChanges) {
199                if (mApplyCalled) throw new IllegalStateException();
200                mClearCalled = true;
201                return this;
202            }
203        }
204
205        @Override
206        public boolean commit() {
207            apply();
208            return true;
209        }
210
211        @Override
212        public void apply() {
213            synchronized (mData) {
214                synchronized (mChanges) {
215                    if (mApplyCalled) throw new IllegalStateException();
216                    if (mClearCalled) {
217                        mData.clear();
218                    }
219                    for (Map.Entry<String, Object> entry : mChanges.entrySet()) {
220                        String key = entry.getKey();
221                        Object value = entry.getValue();
222                        if (value == this) {
223                            // Special value for removal
224                            mData.remove(key);
225                        } else {
226                            mData.put(key, value);
227                        }
228                    }
229                    // The real shared prefs clears out the temporaries allowing the caller to
230                    // reuse the Editor instance, however this is undocumented behavior and subtle
231                    // to read, so instead we just ban any future use of this instance.
232                    mApplyCalled = true;
233                }
234            }
235        }
236    }
237
238}
239