| /* |
| * 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); |
| } |
| } |
| } |