diskSerializer,
57 | int maxDiskSizeBytes,
58 | File diskFolder
59 | ) {
60 | this.appVersion = appVersion;
61 | this.ramMode = ramMode;
62 | this.ramSerializer = ramSerializer;
63 | this.diskMode = diskMode;
64 | this.diskSerializer = diskSerializer;
65 | this.diskCacheFolder = diskFolder;
66 | this.logger = logger;
67 | this.loggerHelper = new LoggerHelper(logger);
68 |
69 | switch (ramMode) {
70 | case ENABLE_WITH_SPECIFIC_SERIALIZER:
71 | this.ramCacheLru = new StringLruCache(maxRamSizeBytes);
72 | break;
73 | case ENABLE_WITH_REFERENCE:
74 | this.ramCacheLru = new ReferenceLruCache<>(maxRamSizeBytes, sizeOf);
75 | break;
76 | default:
77 | this.ramCacheLru = null;
78 | }
79 |
80 | switch (diskMode) {
81 | case ENABLE_WITH_SPECIFIC_SERIALIZER:
82 | this.maxDiskSizeBytes = maxDiskSizeBytes;
83 | try {
84 | openDiskLruCache(diskFolder);
85 | } catch (IOException e) {
86 | logger.logError(e);
87 | }
88 | break;
89 | default:
90 | this.maxDiskSizeBytes = 0;
91 | }
92 | }
93 |
94 | private void openDiskLruCache(File diskFolder) throws IOException {
95 | this.diskLruCache = DiskLruCache.open(
96 | diskFolder,
97 | this.appVersion,
98 | VALUES_PER_CACHE_ENTRY,
99 | this.maxDiskSizeBytes
100 | );
101 | }
102 |
103 | public long getRamUsedInBytes() {
104 | if (ramCacheLru == null) {
105 | return -1;
106 | } else {
107 | return ramCacheLru.size();
108 | }
109 | }
110 |
111 | public long getDiskUsedInBytes() {
112 | if (diskLruCache == null) {
113 | return -1;
114 | } else {
115 | return diskLruCache.size();
116 | }
117 |
118 | }
119 |
120 | /**
121 | * Return the way objects are cached in RAM layer.
122 | *
123 | * @return the way objects are cached in RAM layer.
124 | */
125 | public DualCacheRamMode getRAMMode() {
126 | return ramMode;
127 | }
128 |
129 | /**
130 | * Return the way objects are cached in disk layer.
131 | *
132 | * @return the way objects are cached in disk layer.
133 | */
134 | public DualCacheDiskMode getDiskMode() {
135 | return diskMode;
136 | }
137 |
138 | /**
139 | * Put an object in cache.
140 | *
141 | * @param key is the key of the object.
142 | * @param object is the object to put in cache.
143 | */
144 | public void put(String key, T object) {
145 | // Synchronize put on each entry. Gives concurrent editions on different entries, and atomic
146 | // modification on the same entry.
147 | if (ramMode.equals(DualCacheRamMode.ENABLE_WITH_REFERENCE)) {
148 | ramCacheLru.put(key, object);
149 | }
150 |
151 | String ramSerialized = null;
152 | if (ramMode.equals(DualCacheRamMode.ENABLE_WITH_SPECIFIC_SERIALIZER)) {
153 | ramSerialized = ramSerializer.toString(object);
154 | ramCacheLru.put(key, ramSerialized);
155 | }
156 |
157 | if (diskMode.equals(DualCacheDiskMode.ENABLE_WITH_SPECIFIC_SERIALIZER)) {
158 | try {
159 | dualCacheLock.lockDiskEntryWrite(key);
160 | DiskLruCache.Editor editor = diskLruCache.edit(key);
161 | if (ramSerializer == diskSerializer) {
162 | // Optimization if using same serializer
163 | editor.set(0, ramSerialized);
164 | } else {
165 | editor.set(0, diskSerializer.toString(object));
166 | }
167 | editor.commit();
168 | } catch (IOException e) {
169 | logger.logError(e);
170 | } finally {
171 | dualCacheLock.unLockDiskEntryWrite(key);
172 | }
173 | }
174 | }
175 |
176 | /**
177 | * Return the object of the corresponding key from the cache. In no object is available,
178 | * return null.
179 | *
180 | * @param key is the key of the object.
181 | * @return the object of the corresponding key from the cache. In no object is available,
182 | * return null.
183 | */
184 | public T get(String key) {
185 |
186 | Object ramResult = null;
187 | String diskResult = null;
188 | DiskLruCache.Snapshot snapshotObject = null;
189 |
190 | // Try to get the object from RAM.
191 | boolean isRamSerialized = ramMode.equals(DualCacheRamMode.ENABLE_WITH_SPECIFIC_SERIALIZER);
192 | boolean isRamReferenced = ramMode.equals(DualCacheRamMode.ENABLE_WITH_REFERENCE);
193 | if (isRamSerialized || isRamReferenced) {
194 | ramResult = ramCacheLru.get(key);
195 | }
196 |
197 | if (ramResult == null) {
198 | // Try to get the cached object from disk.
199 | loggerHelper.logEntryForKeyIsNotInRam(key);
200 | if (diskMode.equals(DualCacheDiskMode.ENABLE_WITH_SPECIFIC_SERIALIZER)) {
201 | try {
202 | dualCacheLock.lockDiskEntryWrite(key);
203 | snapshotObject = diskLruCache.get(key);
204 | } catch (IOException e) {
205 | logger.logError(e);
206 | } finally {
207 | dualCacheLock.unLockDiskEntryWrite(key);
208 | }
209 |
210 | if (snapshotObject != null) {
211 | loggerHelper.logEntryForKeyIsOnDisk(key);
212 | try {
213 | diskResult = snapshotObject.getString(0);
214 | } catch (IOException e) {
215 | logger.logError(e);
216 | }
217 | } else {
218 | loggerHelper.logEntryForKeyIsNotOnDisk(key);
219 | }
220 | }
221 |
222 | T objectFromStringDisk = null;
223 |
224 | if (diskResult != null) {
225 | // Load object, no need to check disk configuration since diskresult != null.
226 | objectFromStringDisk = diskSerializer.fromString(diskResult);
227 |
228 | // Refresh object in ram.
229 | if (ramMode.equals(DualCacheRamMode.ENABLE_WITH_REFERENCE)) {
230 | if (diskMode.equals(DualCacheDiskMode.ENABLE_WITH_SPECIFIC_SERIALIZER)) {
231 | ramCacheLru.put(key, objectFromStringDisk);
232 | }
233 | } else if (ramMode.equals(DualCacheRamMode.ENABLE_WITH_SPECIFIC_SERIALIZER)) {
234 | if (diskSerializer == ramSerializer) {
235 | ramCacheLru.put(key, diskResult);
236 | } else {
237 | ramCacheLru.put(key, ramSerializer.toString(objectFromStringDisk));
238 | }
239 | }
240 | return objectFromStringDisk;
241 | }
242 | } else {
243 | loggerHelper.logEntryForKeyIsInRam(key);
244 | if (ramMode.equals(DualCacheRamMode.ENABLE_WITH_REFERENCE)) {
245 | return (T) ramResult;
246 | } else if (ramMode.equals(DualCacheRamMode.ENABLE_WITH_SPECIFIC_SERIALIZER)) {
247 | return ramSerializer.fromString((String) ramResult);
248 | }
249 | }
250 |
251 | // No data is available.
252 | return null;
253 | }
254 |
255 | /**
256 | * Delete the corresponding object in cache.
257 | *
258 | * @param key is the key of the object.
259 | */
260 | public void delete(String key) {
261 | if (!ramMode.equals(DualCacheRamMode.DISABLE)) {
262 | ramCacheLru.remove(key);
263 | }
264 | if (!diskMode.equals(DualCacheDiskMode.DISABLE)) {
265 | try {
266 | dualCacheLock.lockDiskEntryWrite(key);
267 | diskLruCache.remove(key);
268 | } catch (IOException e) {
269 | logger.logError(e);
270 | } finally {
271 | dualCacheLock.unLockDiskEntryWrite(key);
272 | }
273 | }
274 | }
275 |
276 | /**
277 | * Remove all objects from cache (both RAM and disk).
278 | */
279 | public void invalidate() {
280 | invalidateDisk();
281 | invalidateRAM();
282 | }
283 |
284 | /**
285 | * Remove all objects from RAM.
286 | */
287 | public void invalidateRAM() {
288 | if (!ramMode.equals(DualCacheRamMode.DISABLE)) {
289 | ramCacheLru.evictAll();
290 | }
291 | }
292 |
293 | /**
294 | * Remove all objects from Disk.
295 | */
296 | public void invalidateDisk() {
297 | if (!diskMode.equals(DualCacheDiskMode.DISABLE)) {
298 | try {
299 | dualCacheLock.lockFullDiskWrite();
300 | diskLruCache.delete();
301 | openDiskLruCache(diskCacheFolder);
302 | } catch (IOException e) {
303 | logger.logError(e);
304 | } finally {
305 | dualCacheLock.unLockFullDiskWrite();
306 | }
307 | }
308 | }
309 |
310 | /**
311 | * Test if an object is present in cache.
312 | * @param key is the key of the object.
313 | * @return true if the object is present in cache, false otherwise.
314 | */
315 | public boolean contains(String key) {
316 | if (!ramMode.equals(DualCacheRamMode.DISABLE) && ramCacheLru.snapshot().containsKey(key)) {
317 | return true;
318 | }
319 | try {
320 | dualCacheLock.lockDiskEntryWrite(key);
321 | if (!diskMode.equals(DualCacheDiskMode.DISABLE) && diskLruCache.get(key) != null) {
322 | return true;
323 | }
324 | } catch (IOException e) {
325 | logger.logError(e);
326 | } finally {
327 | dualCacheLock.unLockDiskEntryWrite(key);
328 | }
329 | return false;
330 | }
331 |
332 | /**
333 | * Closes the underlying Disk LRU Cache. (if one is in use)
334 | * @throws IOException if an I/O error occurs
335 | * @see DiskLruCache#close()
336 | */
337 | @Override
338 | public void close() throws IOException {
339 | if (diskLruCache != null) {
340 | diskLruCache.close();
341 | }
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/dualcache-library/src/main/java/com/vincentbrison/openlibraries/android/dualcache/RamLruCache.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.vincentbrison.openlibraries.android.dualcache;
18 |
19 | import java.lang.reflect.InvocationTargetException;
20 | import java.util.LinkedHashMap;
21 | import java.util.Map;
22 |
23 | /**
24 | * A cache that holds strong references to a limited number of values. Each time
25 | * a value is accessed, it is moved to the head of a queue. When a value is
26 | * added to a full cache, the value at the end of that queue is evicted and may
27 | * become eligible for garbage collection.
28 | *
29 | * If your cached values hold resources that need to be explicitly released,
30 | * override {@link #entryRemoved}.
31 | *
32 | *
If a cache miss should be computed on demand for the corresponding keys,
33 | * override {@link #create}. This simplifies the calling code, allowing it to
34 | * assume a value will always be returned, even when there's a cache miss.
35 | *
36 | *
By default, the cache size is measured in the number of entries. Override
37 | * {@link #sizeOf} to size the cache in different units. For example, this cache
38 | * is limited to 4MiB of bitmaps:
39 | *
{@code
40 | * int cacheSize = 4 * 1024 * 1024; // 4MiB
41 | * LruCache bitmapCache = new LruCache(cacheSize) {
42 | * protected int sizeOf(String key, Bitmap value) {
43 | * return value.getByteCount();
44 | * }
45 | * }}
46 | *
47 | * This class is thread-safe. Perform multiple cache operations atomically by
48 | * synchronizing on the cache:
{@code
49 | * synchronized (cache) {
50 | * if (cache.get(key) == null) {
51 | * cache.put(key, value);
52 | * }
53 | * }}
54 | *
55 | * This class does not allow null to be used as a key or value. A return
56 | * value of null from {@link #get}, {@link #put} or {@link #remove} is
57 | * unambiguous: the key was not in the cache.
58 | *
59 | *
This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
60 | * of Android's
61 | * Support Package for earlier releases.
62 | */
63 | class RamLruCache {
64 | private final LinkedHashMap map;
65 |
66 | /** Size of this cache in units. Not necessarily the number of elements. */
67 | private int size;
68 | private int maxSize;
69 |
70 | private int putCount;
71 | private int createCount;
72 | private int evictionCount;
73 | private int hitCount;
74 | private int missCount;
75 |
76 | /**
77 | * @param maxSize for caches that do not override {@link #sizeOf}, this is
78 | * the maximum number of entries in the cache. For all other caches,
79 | * this is the maximum sum of the sizes of the entries in this cache.
80 | */
81 | public RamLruCache(int maxSize) {
82 | if (maxSize <= 0) {
83 | throw new IllegalArgumentException("maxSize <= 0");
84 | }
85 | this.maxSize = maxSize;
86 | this.map = new LinkedHashMap(0, 0.75f, true);
87 | }
88 |
89 | /**
90 | * Sets the size of the cache.
91 | *
92 | * @param maxSize The new maximum size.
93 | */
94 | public void resize(int maxSize) {
95 | if (maxSize <= 0) {
96 | throw new IllegalArgumentException("maxSize <= 0");
97 | }
98 |
99 | synchronized (this) {
100 | this.maxSize = maxSize;
101 | }
102 | trimToSize(maxSize);
103 | }
104 |
105 | /**
106 | * Returns the value for {@code key} if it exists in the cache or can be
107 | * created by {@code #create}. If a value was returned, it is moved to the
108 | * head of the queue. This returns null if a value is not cached and cannot
109 | * be created.
110 | */
111 | public final V get(K key) {
112 | if (key == null) {
113 | throw new NullPointerException("key == null");
114 | }
115 |
116 | V mapValue;
117 | synchronized (this) {
118 | mapValue = map.get(key);
119 | if (mapValue != null) {
120 | hitCount++;
121 | return mapValue;
122 | }
123 | missCount++;
124 | }
125 |
126 | /*
127 | * Attempt to create a value. This may take a long time, and the map
128 | * may be different when create() returns. If a conflicting value was
129 | * added to the map while create() was working, we leave that value in
130 | * the map and release the created value.
131 | */
132 |
133 | V createdValue = create(key);
134 | if (createdValue == null) {
135 | return null;
136 | }
137 |
138 | synchronized (this) {
139 | createCount++;
140 | mapValue = map.put(key, createdValue);
141 |
142 | if (mapValue != null) {
143 | // There was a conflict so undo that last put
144 | map.put(key, mapValue);
145 | } else {
146 | size += safeSizeOf(key, createdValue);
147 | }
148 | }
149 |
150 | if (mapValue != null) {
151 | entryRemoved(false, key, createdValue, mapValue);
152 | return mapValue;
153 | } else {
154 | trimToSize(maxSize);
155 | return createdValue;
156 | }
157 | }
158 |
159 | /**
160 | * Caches {@code value} for {@code key}. The value is moved to the head of
161 | * the queue.
162 | *
163 | * @return the previous value mapped by {@code key}.
164 | */
165 | public final V put(K key, V value) {
166 | if (key == null || value == null) {
167 | throw new NullPointerException("key == null || value == null");
168 | }
169 |
170 | V previous;
171 | synchronized (this) {
172 | putCount++;
173 | size += safeSizeOf(key, value);
174 | previous = map.put(key, value);
175 | if (previous != null) {
176 | size -= safeSizeOf(key, previous);
177 | }
178 | }
179 |
180 | if (previous != null) {
181 | entryRemoved(false, key, previous, value);
182 | }
183 |
184 | trimToSize(maxSize);
185 | return previous;
186 | }
187 |
188 | /**
189 | * Remove the eldest entries until the total of remaining entries is at or
190 | * below the requested size.
191 | *
192 | * @param maxSize the maximum size of the cache before returning. May be -1
193 | * to evict even 0-sized elements.
194 | */
195 | public void trimToSize(int maxSize) {
196 | while (true) {
197 | K key;
198 | V value;
199 | synchronized (this) {
200 | if (size < 0 || (map.isEmpty() && size != 0)) {
201 | throw new IllegalStateException(getClass().getName()
202 | + ".sizeOf() is reporting inconsistent results!");
203 | }
204 |
205 | if (size <= maxSize) {
206 | break;
207 | }
208 | Map.Entry toEvict = null;
209 | try {
210 | toEvict = (Map.Entry) map.getClass().getMethod("eldest").invoke(map);
211 |
212 | } catch (NoSuchMethodException e) {
213 | e.printStackTrace();
214 | } catch (InvocationTargetException e) {
215 | e.printStackTrace();
216 | } catch (IllegalAccessException e) {
217 | e.printStackTrace();
218 | }
219 | if (toEvict == null) {
220 | break;
221 | }
222 | key = toEvict.getKey();
223 | value = toEvict.getValue();
224 | map.remove(key);
225 | size -= safeSizeOf(key, value);
226 | evictionCount++;
227 | }
228 | entryRemoved(true, key, value, null);
229 | }
230 | }
231 |
232 | /**
233 | * Removes the entry for {@code key} if it exists.
234 | *
235 | * @return the previous value mapped by {@code key}.
236 | */
237 | public final V remove(K key) {
238 | if (key == null) {
239 | throw new NullPointerException("key == null");
240 | }
241 |
242 | V previous;
243 | synchronized (this) {
244 | previous = map.remove(key);
245 | if (previous != null) {
246 | size -= safeSizeOf(key, previous);
247 | }
248 | }
249 |
250 | if (previous != null) {
251 | entryRemoved(false, key, previous, null);
252 | }
253 |
254 | return previous;
255 | }
256 |
257 | /**
258 | * Called for entries that have been evicted or removed. This method is
259 | * invoked when a value is evicted to make space, removed by a call to
260 | * {@link #remove}, or replaced by a call to {@link #put}. The default
261 | * implementation does nothing.
262 | *
263 | * The method is called without synchronization: other threads may
264 | * access the cache while this method is executing.
265 | *
266 | * @param evicted true if the entry is being removed to make space, false
267 | * if the removal was caused by a {@link #put} or {@link #remove}.
268 | * @param newValue the new value for {@code key}, if it exists. If non-null,
269 | * this removal was caused by a {@link #put}. Otherwise it was caused by
270 | * an eviction or a {@link #remove}.
271 | */
272 | protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
273 |
274 | /**
275 | * Called after a cache miss to compute a value for the corresponding key.
276 | * Returns the computed value or null if no value can be computed. The
277 | * default implementation returns null.
278 | *
279 | *
The method is called without synchronization: other threads may
280 | * access the cache while this method is executing.
281 | *
282 | *
If a value for {@code key} exists in the cache when this method
283 | * returns, the created value will be released with {@link #entryRemoved}
284 | * and discarded. This can occur when multiple threads request the same key
285 | * at the same time (causing multiple values to be created), or when one
286 | * thread calls {@link #put} while another is creating a value for the same
287 | * key.
288 | */
289 | protected V create(K key) {
290 | return null;
291 | }
292 |
293 | private int safeSizeOf(K key, V value) {
294 | int result = sizeOf(key, value);
295 | if (result < 0) {
296 | throw new IllegalStateException("Negative size: " + key + "=" + value);
297 | }
298 | return result;
299 | }
300 |
301 | /**
302 | * Returns the size of the entry for {@code key} and {@code value} in
303 | * user-defined units. The default implementation returns 1 so that size
304 | * is the number of entries and max size is the maximum number of entries.
305 | *
306 | *
An entry's size must not change while it is in the cache.
307 | */
308 | protected int sizeOf(K key, V value) {
309 | return 1;
310 | }
311 |
312 | /**
313 | * Clear the cache, calling {@link #entryRemoved} on each removed entry.
314 | */
315 | public final void evictAll() {
316 | trimToSize(-1); // -1 will evict 0-sized elements
317 | }
318 |
319 | /**
320 | * For caches that do not override {@link #sizeOf}, this returns the number
321 | * of entries in the cache. For all other caches, this returns the sum of
322 | * the sizes of the entries in this cache.
323 | */
324 | public synchronized final int size() {
325 | return size;
326 | }
327 |
328 | /**
329 | * For caches that do not override {@link #sizeOf}, this returns the maximum
330 | * number of entries in the cache. For all other caches, this returns the
331 | * maximum sum of the sizes of the entries in this cache.
332 | */
333 | public synchronized final int maxSize() {
334 | return maxSize;
335 | }
336 |
337 | /**
338 | * Returns the number of times {@link #get} returned a value that was
339 | * already present in the cache.
340 | */
341 | public synchronized final int hitCount() {
342 | return hitCount;
343 | }
344 |
345 | /**
346 | * Returns the number of times {@link #get} returned null or required a new
347 | * value to be created.
348 | */
349 | public synchronized final int missCount() {
350 | return missCount;
351 | }
352 |
353 | /**
354 | * Returns the number of times {@link #create(Object)} returned a value.
355 | */
356 | public synchronized final int createCount() {
357 | return createCount;
358 | }
359 |
360 | /**
361 | * Returns the number of times {@link #put} was called.
362 | */
363 | public synchronized final int putCount() {
364 | return putCount;
365 | }
366 |
367 | /**
368 | * Returns the number of values that have been evicted.
369 | */
370 | public synchronized final int evictionCount() {
371 | return evictionCount;
372 | }
373 |
374 | /**
375 | * Returns a copy of the current contents of the cache, ordered from least
376 | * recently accessed to most recently accessed.
377 | */
378 | public synchronized final Map snapshot() {
379 | return new LinkedHashMap(map);
380 | }
381 |
382 | @Override public synchronized final String toString() {
383 | int accesses = hitCount + missCount;
384 | int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
385 | return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
386 | maxSize, hitCount, missCount, hitPercent);
387 | }
388 | }
389 |
--------------------------------------------------------------------------------
/quality/checkstyle/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
264 |
265 |
266 |
267 |
268 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
365 |
366 |
367 |
368 |
369 |
370 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
--------------------------------------------------------------------------------