blob: ff7a12fb223d92ddc77b5faeff3786f9a411dea8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.sis.internal.jaxb;
import java.util.Map;
import java.util.IdentityHashMap;
import java.lang.reflect.Modifier;
import org.apache.sis.xml.NilReason;
/**
* A workaround for attaching properties ({@code nilreason}, {@code href}, <i>etc.</i>) to primitive type wrappers.
* The normal approach in SIS is to implement the {@link org.apache.sis.xml.NilObject} interface. However we can not
* do so when the object is a final Java class like {@link Boolean}, {@link Integer}, {@link Double} or {@link String}.
* This class provides a workaround using specific instances of those wrappers.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.4
*
* @see NilReason#createNilObject(Class)
*
* @since 0.4
* @module
*/
public final class PrimitiveTypeProperties {
/**
* The map where to store specific instances. Keys are instances of the primitive wrappers considered as nil.
* Values are the {@code NilReason} why the primitive is missing, or any other property we may want to attach.
*
* <h4>Identity comparisons</h4>
* We really need an identity hash map; using the {@code Object.equals(Object)} method is not allowed here.
* This is because "nil values" are real values. For example if the type is {@link Integer}, then the nil value
* is an {@code Integer} instance having the value 0. We don't want to consider every 0 integer value as nil,
* but only the specific {@code Integer} instance used as sentinel value for nil.
*
* <h4>Weak references</h4>
* We can not use weak value references, because we don't want the {@link NilReason} (the map value) to be lost
* while the sentinel value (the map key) is still in use. We could use weak references for the keys, but JDK 7
* does not provides any map implementation which is both an {@code IdentityHashMap} and a {@code WeakHashMap}.
*
* For now we do not use weak references. This means that if a user creates a custom {@code NilReason} by a call
* to {@link NilReason#valueOf(String)}, and if he uses that nil reason for a primitive type, then that custom
* {@code NilReason} instance and its sentinel values will never be garbage-collected.
* We presume that such cases will be rare enough for not being an issue in practice.
*
* <h4>Synchronization</h4>
* All accesses to this map shall be synchronized on the map object.
*/
private static final Map<Object,Object> SENTINEL_VALUES = new IdentityHashMap<>();
/**
* Do not allow instantiation of this class.
*/
private PrimitiveTypeProperties() {
}
/**
* Returns {@code true} if the given type is a valid key. This {@code PrimitiveTypeProperties}
* class is a workaround to be used only for final classes on which we have no control.
* Non-final classes shall implement {@link org.apache.sis.xml.NilObject} instead.
*/
private static boolean isValidKey(final Object primitive) {
return Modifier.isFinal(primitive.getClass().getModifiers());
}
/**
* Associates the given property to the given primitive.
* The {@code primitive} argument shall be a specific instance created by the {@code new} keyword, not
* a shared instance link {@link Boolean#FALSE} or the values returned by {@link Integer#valueOf(int)}.
*
* @param primitive the {@link Boolean}, {@link Integer}, {@link Double} or {@link String} specific instance.
* @param property the {@link NilReason} or other property to associate to the given instance.
*/
public static void associate(final Object primitive, final Object property) {
assert isValidKey(primitive) : primitive;
synchronized (SENTINEL_VALUES) {
final Object old = SENTINEL_VALUES.put(primitive, property);
if (old != null) { // Should never happen - this is rather debugging check.
SENTINEL_VALUES.put(primitive, old);
throw new AssertionError(primitive);
}
}
}
/**
* Returns the property of the given primitive type, or {@code null} if none.
*
* @param primitive the {@link Boolean}, {@link Integer}, {@link Double} or {@link String} specific instance.
* @return the property associated to the given instance, or {@code null} if none.
*/
public static Object property(final Object primitive) {
/*
* No 'assert isValidKey(primitive)' because this method is sometime invoked
* only after a brief inspection (e.g. 'NilReason.mayBeNil(Object)' method).
*/
synchronized (SENTINEL_VALUES) {
return SENTINEL_VALUES.get(primitive);
}
}
}