blob: 62b60739b004a46daa4fbd85360c2080ae96aa5b [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.user.client.rpc.core.java.util;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
* Custom field serializer for {@link java.util.LinkedHashMap} for the server
* (uses reflection).
*/
@SuppressWarnings("unchecked")
public final class LinkedHashMap_CustomFieldSerializer {
/**
* We use an atomic reference to avoid having to synchronize. This is safe
* because it's only used as a cache; it's okay to read a stale value.
*/
private static AtomicReference<Field> accessOrderField = new AtomicReference<Field>(
null);
private static Object KEY1 = new Object();
private static Object KEY2 = new Object();
/**
* We use an atomic reference to avoid having to synchronize. This is safe
* because it's only used as a cache; it's okay to read a stale value.
*/
private static AtomicBoolean reflectionHasFailed = new AtomicBoolean(false);
public static void deserialize(SerializationStreamReader streamReader,
LinkedHashMap instance) throws SerializationException {
Map_CustomFieldSerializerBase.deserialize(streamReader, instance);
}
/**
* Infers the value of the private accessOrder field of instance by examining
* its behavior on a set of test inputs, without using reflection. Note that
* this implementation clones the instance, which could be slow.
*
* @param instance the instance to check
* @return the value of instance.accessOrder
*/
public static boolean getAccessOrderNoReflection(LinkedHashMap instance) {
/*
* Clone the instance so our modifications won't affect the original. In
* particular, if the original overrides removeEldestEntry, adding elements
* to the map could cause existing elements to be removed.
*/
instance = (LinkedHashMap) instance.clone();
instance.clear();
/*
* We insert key1, then key2, after which we access key1. We then iterate
* over the key set and observe the order in which keys are returned. The
* iterator will return keys in the order of least recent insertion or
* access, depending on the value of the accessOrder field within the
* LinkedHashMap instance. If the iterator is ordered by least recent
* insertion (accessOrder = false), we will encounter key1 first since key2
* has been inserted more recently. If it is ordered by least recent access
* (accessOrder = true), we will encounter key2 first, since key1 has been
* accessed more recently.
*/
instance.put(KEY1, KEY1); // INSERT key1
instance.put(KEY2, KEY2); // INSERT key2
instance.get(KEY1); // ACCESS key1
return instance.keySet().iterator().next() == KEY2;
}
public static LinkedHashMap instantiate(SerializationStreamReader streamReader)
throws SerializationException {
boolean accessOrder = streamReader.readBoolean();
return new LinkedHashMap(16, .75f, accessOrder);
}
public static void serialize(SerializationStreamWriter streamWriter,
LinkedHashMap instance) throws SerializationException {
streamWriter.writeBoolean(getAccessOrder(instance));
Map_CustomFieldSerializerBase.serialize(streamWriter, instance);
}
private static boolean getAccessOrder(LinkedHashMap instance) {
if (!reflectionHasFailed.get()) {
try {
Field f = accessOrderField.get();
if (f == null || !f.isAccessible()) {
f = LinkedHashMap.class.getDeclaredField("accessOrder");
synchronized (f) {
// Ensure all threads can see the accessibility.
f.setAccessible(true);
}
accessOrderField.set(f);
}
return ((Boolean) f.get(instance)).booleanValue();
} catch (SecurityException e) {
// fall through
} catch (NoSuchFieldException e) {
// fall through
} catch (IllegalArgumentException e) {
// fall through
} catch (IllegalAccessException e) {
// fall through
}
reflectionHasFailed.set(true);
}
// Use a (possibly slower) technique that does not require reflection.
return getAccessOrderNoReflection(instance);
}
}