Java tutorial
/* * Copyright (c) 2016 by European Commission * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/european-union-public-licence-eupl-v.1.1 * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. * */ package eu.eidas.auth.commons.attribute; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamException; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.ThreadSafe; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.apache.commons.collections.CollectionUtils; import eu.eidas.auth.commons.io.MapSerializationHelper; import eu.eidas.util.Preconditions; /** * An immutable Map of {@link AttributeDefinition}s to {@link AttributeValue}s. * * @since 1.1 */ @SuppressWarnings("ConstantConditions") @Immutable @ThreadSafe public final class ImmutableAttributeMap implements Serializable { /** * Effective Java, 2nd Ed. : Item 78: Serialization Proxy pattern. * <p> * The serialVersionUID of this class is not used because a Serialization Proxy is used instead. */ private static final long serialVersionUID = 3407315670529047180L; /** * Builder pattern for the {@link ImmutableAttributeMap} class. * <p/> * Effective Java, 2nd Ed. : Item 2: Builder Pattern. * <p/> * This Builder is not thread-safe but is thread-compliant, it is supposed to be used by only one thread. * <p/> */ @SuppressWarnings("ParameterHidesMemberVariable") @NotThreadSafe public static final class Builder { @Nonnull private static <T> AttributeValue<T> unmarshal(@Nonnull AttributeDefinition<T> attribute, @Nonnull String value, boolean isNonLatinScriptAlternateVersion) { AttributeValue<T> attributeValue; try { attributeValue = attribute.unmarshal(value, isNonLatinScriptAlternateVersion); } catch (AttributeValueMarshallingException ame) { throw new IllegalArgumentException(ame); } return attributeValue; } /** * Typesafe heterogeneous container pattern. * <p> * See item 29 of Effective Java 2nd Edition. */ @Nonnull private final Map<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> definitionsToValues = new LinkedHashMap<>(); @Nonnull private final Map<URI, AttributeDefinition<?>> nameUrisToDefinitions = new LinkedHashMap<>(); @Nonnull private final Map<URI, ImmutableSet<? extends AttributeValue<?>>> nameUrisToValues = new LinkedHashMap<>(); @Nonnull private final Map<String, Set<AttributeDefinition<?>>> friendlyNamesToDefinitions = new LinkedHashMap<>(); public Builder() { } public Builder(@Nonnull Builder copy) { Preconditions.checkNotNull(copy, "copy"); putAll(copy.definitionsToValues); } public Builder(@Nonnull ImmutableAttributeMap copy) { Preconditions.checkNotNull(copy, "copy"); putAll(copy); } public Builder(@Nonnull Map<AttributeDefinition<?>, ? extends Iterable<? extends AttributeValue<?>>> copy) { Preconditions.checkNotNull(copy, "copy"); putAll(copy); } @Nonnull public ImmutableAttributeMap build() { ImmutableAttributeMap result = new ImmutableAttributeMap(this); if (result.isEmpty()) { return EMPTY; } return result; } @Nonnull public <T> Builder put(@Nonnull AttributeDefinition<T> attribute) { Preconditions.checkNotNull(attribute, "attribute"); ImmutableSet<? extends AttributeValue<T>> valueSet = ImmutableSet.of(); return put(attribute, valueSet); } @Nonnull private <T> Builder put(@Nonnull AttributeDefinition<T> attribute, @Nonnull ImmutableSet<? extends AttributeValue<T>> values) { Preconditions.checkNotNull(attribute, "attribute"); Preconditions.checkNotNull(values, "values"); // Typesafe heterogeneous container pattern. // See item 29 of Effective Java 2nd Edition. // Typesafety check using class#cast(): Class<T> parameterizedType = attribute.getParameterizedType(); for (final AttributeValue<T> attributeValue : values) { Preconditions.checkNotNull(attributeValue, "attributeValue"); T value = attributeValue.getValue(); Preconditions.checkNotNull(value, "value"); // ensure all values can be cast to the actual parameterized class: parameterizedType.cast(value); } if (definitionsToValues.containsKey(attribute)) { throw new IllegalArgumentException("Duplicate values for attribute \"" + attribute + "\""); } URI nameUri = attribute.getNameUri(); AttributeDefinition<?> existing = nameUrisToDefinitions.get(nameUri); if (null != existing) { throw new IllegalArgumentException( "Non-unique attribute name URIs for 2 attributes: " + attribute + " and " + existing); } //noinspection unchecked unfortunate but due to the heterogeneous container pattern definitionsToValues.put(attribute, (ImmutableSet<? extends AttributeValue<?>>) (Object) values); nameUrisToDefinitions.put(nameUri, attribute); //noinspection unchecked unfortunate but due to the heterogeneous container pattern nameUrisToValues.put(nameUri, (ImmutableSet<? extends AttributeValue<?>>) (Object) values); String friendlyName = attribute.getFriendlyName(); Set<AttributeDefinition<?>> attributeDefinitions = friendlyNamesToDefinitions.get(friendlyName); if (null == attributeDefinitions) { attributeDefinitions = new LinkedHashSet<>(); friendlyNamesToDefinitions.put(friendlyName, attributeDefinitions); } attributeDefinitions.add(attribute); return this; } @Nonnull public <T> Builder put(@Nonnull AttributeDefinition<T> attribute, @Nonnull Iterable<? extends AttributeValue<T>> values) { Preconditions.checkNotNull(attribute, "attribute"); Preconditions.checkNotNull(values, "values"); ImmutableSet<? extends AttributeValue<T>> valueSet = ImmutableSet.copyOf(values); return put(attribute, valueSet); } @Nonnull public <T> Builder put(@Nonnull AttributeDefinition<T> attribute, @Nonnull AttributeValue<T>... values) { Preconditions.checkNotNull(attribute, "attribute"); Preconditions.checkNotNull(values, "values"); ImmutableSet<? extends AttributeValue<T>> valueSet = ImmutableSet.copyOf(values); return put(attribute, valueSet); } @Nonnull public <T> Builder put(@Nonnull AttributeDefinition<T> attribute, @Nonnull String singleMarshalledValue) { Preconditions.checkNotNull(attribute, "attribute"); Preconditions.checkNotNull(singleMarshalledValue, "singleMarshalledValue"); return put(attribute, ImmutableSet.of(unmarshal(attribute, singleMarshalledValue, false))); } /** * put all a collection of attributeDefinitions without values * * @param attributeDefinitions the collection of attributeDefinitions * @return the builder itself */ @Nonnull public Builder putAll(@Nonnull Collection<AttributeDefinition<?>> attributeDefinitions) { Preconditions.checkNotNull(attributeDefinitions, "attributeDefinitions"); for (AttributeDefinition<?> attributeDefinition : attributeDefinitions) { put(attributeDefinition); } return this; } @Nonnull public Builder putAll( @Nonnull Map<AttributeDefinition<?>, ? extends Iterable<? extends AttributeValue<?>>> attributeMap) { Preconditions.checkNotNull(attributeMap, "attributeMap"); for (final Map.Entry<AttributeDefinition<?>, ? extends Iterable<? extends AttributeValue<?>>> entry : attributeMap .entrySet()) { //noinspection unchecked,rawtypes unfortunate but due to the heterogeneous container pattern put((AttributeDefinition) entry.getKey(), (ImmutableSet) ImmutableSet.copyOf(entry.getValue())); } return this; } @Nonnull public Builder putAll(@Nonnull ImmutableAttributeMap attributeMap) { Preconditions.checkNotNull(attributeMap, "attributeMap"); putAll(attributeMap.attributeMap); return this; } /** * Puts only values which are not non-latin-script alternate versions. * * @param attribute the definition * @param primaryValues values which are not non-latin-script alternate versions * @return this Builder */ @Nonnull public Builder putPrimaryValues(@Nonnull AttributeDefinition<?> attribute, @Nonnull Iterable<String> primaryValues) { Preconditions.checkNotNull(attribute, "attribute"); Preconditions.checkNotNull(primaryValues, "primaryValues"); ImmutableSet.Builder<AttributeValue<?>> setBuilder = ImmutableSet.builder(); for (final String primaryValue : primaryValues) { setBuilder.add(unmarshal(attribute, primaryValue, false)); } return put((AttributeDefinition) attribute, (ImmutableSet) setBuilder.build()); } } /** * A typesafe and immutable map entry (key-value pair). * * @see #entrySet() */ @Immutable public static final class ImmutableAttributeEntry<T> { @Nonnull private final AttributeDefinition<T> attributeDefinition; @Nonnull private final ImmutableSet<? extends AttributeValue<T>> attributeValues; public ImmutableAttributeEntry(@Nonnull AttributeDefinition<T> attributeDefinition, @Nonnull ImmutableSet<? extends AttributeValue<T>> attributeValues) { this.attributeDefinition = attributeDefinition; this.attributeValues = attributeValues; } /** * Returns the key corresponding to this entry. * * @return the key corresponding to this entry */ @Nonnull public AttributeDefinition<T> getKey() { return attributeDefinition; } /** * Returns the value corresponding to this entry. * * @return the value corresponding to this entry */ @Nonnull public ImmutableSet<? extends AttributeValue<T>> getValues() { return attributeValues; } } /** * Effective Java, 2nd Ed. : Item 78: Serialization Proxy pattern. * <p/> * Defensive serialization ensuring that the validation rules defined in the Builder are always used. */ private static final class SerializationProxy implements Serializable { private static final long serialVersionUID = -6933762015200310735L; @Nonnull private transient ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> attributeMap; private SerializationProxy(@Nonnull ImmutableAttributeMap attributeMap) { this.attributeMap = attributeMap.attributeMap; } private void readObject(@Nonnull ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); Map<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> map = new LinkedHashMap<>(); MapSerializationHelper.readMap(in, map); attributeMap = ImmutableMap.copyOf(map); } private void writeObject(@Nonnull ObjectOutputStream out) throws IOException { out.defaultWriteObject(); MapSerializationHelper.writeMap(out, attributeMap); } // The declarations below are necessary for serialization /** * Defensive serialization ensuring that the validation rules defined in the Builder are always used. * <p/> * Used upon de-serialization, not serialization. * <p/> * The state of this class is transformed back into the class it represents. */ private Object readResolve() throws ObjectStreamException { return new Builder().putAll(attributeMap).build(); } } private static final ImmutableAttributeMap EMPTY = new ImmutableAttributeMap(new Builder()); @Nonnull public static ImmutableAttributeMap.Builder builder() { return new Builder(); } @Nonnull public static ImmutableAttributeMap.Builder builder(@Nonnull Builder copy) { return new Builder(copy); } @Nonnull public static ImmutableAttributeMap.Builder builder(@Nonnull ImmutableAttributeMap copy) { return new Builder(copy); } @Nonnull public static ImmutableAttributeMap.Builder builder( @Nonnull Map<AttributeDefinition<?>, ? extends Iterable<? extends AttributeValue<?>>> copy) { return new Builder(copy); } @Nonnull public static ImmutableAttributeMap copyOf( @Nonnull Map<AttributeDefinition<?>, ? extends Iterable<? extends AttributeValue<?>>> map) { return new Builder(map).build(); } @Nonnull public static ImmutableAttributeMap of() { return EMPTY; } @Nonnull public static <T> ImmutableAttributeMap of(@Nonnull AttributeDefinition<T> attributeDefinition, @Nonnull Iterable<? extends AttributeValue<T>> values) { return new Builder().put(attributeDefinition, values).build(); } @Nonnull public static <T> ImmutableAttributeMap of(@Nonnull AttributeDefinition<T> attributeDefinition, @Nonnull AttributeValue<T>... values) { return new Builder().put(attributeDefinition, values).build(); } @Nonnull private static URI toUri(@Nonnull String name) { URI nameUri; try { nameUri = new URI(name); } catch (URISyntaxException use) { throw new IllegalArgumentException("Invalid name URI \"" + name + "\": " + use, use); } return nameUri; } @Nullable public static ImmutableValueMap toValueMap( @Nullable Map<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> map) { if (null == map) { return null; } ImmutableValueMap.Builder builder = ImmutableValueMap.builder(); for (final Map.Entry<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> entry : map .entrySet()) { builder.put(entry.getKey(), toValues((ImmutableSet) entry.getValue())); } return builder.build(); } @Nullable public static <T> ImmutableSet<? extends T> toValues( @Nullable Iterable<? extends AttributeValue<T>> attributeValues) { if (null == attributeValues) { return null; } ImmutableSet.Builder<T> builder = ImmutableSet.builder(); for (final AttributeValue<T> attributeValue : attributeValues) { builder.add(attributeValue.getValue()); } return builder.build(); } @Nonnull private final transient ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> attributeMap; @Nonnull private final transient ImmutableMap<URI, AttributeDefinition<?>> nameUrisToDefinitions; @Nonnull private final transient ImmutableMap<URI, ImmutableSet<? extends AttributeValue<?>>> nameUrisToValues; @Nonnull private final transient ImmutableMap<String, ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>>> friendlyNameMap; @Nonnull private final transient ImmutableValueMap valueMap; private ImmutableAttributeMap(@Nonnull Builder builder) { attributeMap = ImmutableMap.copyOf(builder.definitionsToValues); nameUrisToDefinitions = ImmutableMap.copyOf(builder.nameUrisToDefinitions); nameUrisToValues = ImmutableMap.copyOf(builder.nameUrisToValues); ImmutableMap.Builder<String, ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>>> friendlyNameMapBuilder = new ImmutableMap.Builder<>(); for (final Map.Entry<String, Set<AttributeDefinition<?>>> entry : builder.friendlyNamesToDefinitions .entrySet()) { String friendlyName = entry.getKey(); Set<AttributeDefinition<?>> attributeDefinitions = entry.getValue(); ImmutableMap.Builder<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> subMapBuilder = new ImmutableMap.Builder<>(); for (final AttributeDefinition<?> attributeDefinition : attributeDefinitions) { subMapBuilder.put(attributeDefinition, attributeMap.get(attributeDefinition)); } friendlyNameMapBuilder.put(friendlyName, subMapBuilder.build()); } friendlyNameMap = friendlyNameMapBuilder.build(); valueMap = toValueMap(attributeMap); } @Nonnull public ImmutableSet<ImmutableAttributeEntry<?>> entrySet() { ImmutableSet.Builder<ImmutableAttributeEntry<?>> builder = ImmutableSet.builder(); for (final Map.Entry<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> entry : attributeMap .entrySet()) { //noinspection unchecked builder.add(new ImmutableAttributeEntry(entry.getKey(), entry.getValue())); } return builder.build(); } /** * Returns the content of this object as an {@link ImmutableMap} where keys are {@link AttributeDefinition}s and * values are {@link ImmutableSet}s of {@link AttributeValue}s. * * @return the content of this object as an {@link ImmutableMap}. */ @Nonnull public ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> getAttributeMap() { return attributeMap; } /** * Returns the {@link AttributeValue}s for the given {@link AttributeDefinition} as an {@link ImmutableSet} or * returns {@code null} if no attribute in this map matches the given {@link AttributeDefinition}. * * @param attributeDefinition the attribute definition to look up. * @return The {@link AttributeValue}s (if any) corresponding to the given {@link AttributeDefinition}. */ @Nullable public <T> ImmutableSet<? extends AttributeValue<T>> getAttributeValues( @Nonnull AttributeDefinition<T> attributeDefinition) { //noinspection unchecked unfortunate but due to the heterogeneous container pattern return (ImmutableSet<? extends AttributeValue<T>>) attributeMap.get(attributeDefinition); } /** * Returns the sub-map of this map where all the keys have the given friendly name or returns {@code null} if no * attribute in this map has that friendly name. * <p> * The returned map has {@link AttributeDefinition}s as Map keys and {@link ImmutableSet} of {@link AttributeValue}s * as Map values. * <p> * If you want to have a sub-map of values instead of {@link AttributeValue}s, use {@link * #getValuesByFriendlyName(String)} instead. * * @param friendlyName the friendly name which returned attribute keys must possess * @return a sub-map of this map where the keys are the {@link AttributeDefinition}s which have the given friendly * name and where the values are {@link ImmutableSet}s of {@link AttributeValue}s or returns {@code null} if no * attribute in this map has that friendly name. */ @Nullable public ImmutableAttributeMap getAttributeValuesByFriendlyName(@Nonnull String friendlyName) { ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> subMap = friendlyNameMap .get(friendlyName); if (null == subMap) { return null; } return builder().putAll(subMap).build(); } @Nullable public <T> ImmutableSet<? extends AttributeValue<T>> getAttributeValuesByNameUri(@Nonnull String name) { URI nameUri = toUri(name); return getAttributeValuesByNameUri(nameUri); } @Nullable public <T> ImmutableSet<? extends AttributeValue<T>> getAttributeValuesByNameUri(@Nonnull URI name) { return (ImmutableSet<? extends AttributeValue<T>>) nameUrisToValues.get(name); } /** * Returns all the {@link AttributeDefinition}s in the map matching the given attribute name URI (i.e. full * attribute name) or returns {@code null} if no attribute in this map matches the given name URI. * * @param name the attribute name URI to look up. * @return all the {@link AttributeDefinition}s in the map matching the given attribute name URI or returns {@code * null} when there is no match. */ @Nullable public <T> AttributeDefinition<T> getDefinitionByNameUri(@Nonnull String name) { URI nameUri = toUri(name); return getDefinitionByNameUri(nameUri); } /** * Returns all the {@link AttributeDefinition}s in the map matching the given attribute name URI (i.e. full * attribute name) or returns {@code null} if no attribute in this map matches the given name URI. * * @param name the attribute name URI to look up. * @return all the {@link AttributeDefinition}s in the map matching the given attribute name URI or returns {@code * null} when there is no match. */ @Nullable public <T> AttributeDefinition<T> getDefinitionByNameUri(@Nonnull URI name) { return (AttributeDefinition<T>) nameUrisToDefinitions.get(name); } /** * Returns all the {@link AttributeDefinition}s in the map, can be empty but never {@code null}. * * @return all the {@link AttributeDefinition}s in the map, can be empty but never {@code null}. */ @Nonnull public ImmutableSet<AttributeDefinition<?>> getDefinitions() { return attributeMap.keySet(); } /** * Returns all the {@link AttributeDefinition}s in the map matching the given friendly name or returns {@code null} * if no attribute in this map matches the given {@link AttributeDefinition}. * * @param friendlyName the attribute friendly name to look up. * @return all the {@link AttributeDefinition}s in the map matching the given friendly name or returns {@code null} * when there is no match. */ @Nullable public ImmutableSet<AttributeDefinition<?>> getDefinitionsByFriendlyName(@Nonnull String friendlyName) { ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> subMap = friendlyNameMap .get(friendlyName); if (null == subMap) { return null; } return subMap.keySet(); } /** * Returns the first {@link AttributeValue} for the given {@link AttributeDefinition} or returns {@code null} if no * attribute in this map matches the given {@link AttributeDefinition}. * * @param attributeDefinition the attribute definition to look up. * @return The first {@link AttributeValue} (if any) corresponding to the given {@link AttributeDefinition}. */ @Nullable public <T> AttributeValue<T> getFirstAttributeValue(@Nonnull AttributeDefinition<T> attributeDefinition) { ImmutableSet<? extends AttributeValue<T>> values = getAttributeValues(attributeDefinition); if (CollectionUtils.isEmpty(values)) { return null; } return values.iterator().next(); } /** * Returns the first typed value for the given {@link AttributeDefinition} or returns {@code null} if no attribute * in this map matches the given {@link AttributeDefinition}. * * @param attributeDefinition the attribute definition to look up. * @return The first value (if any) corresponding to the given {@link AttributeDefinition}. */ @Nullable public <T> T getFirstValue(@Nonnull AttributeDefinition<T> attributeDefinition) { return valueMap.getFirstValue(attributeDefinition); } /** * Returns the content of this object as an {@link ImmutableValueMap} where keys are {@link AttributeDefinition}s * and values are {@link ImmutableSet}s of typed values. * * @return the content of this object as an {@link ImmutableValueMap}. */ @Nonnull public ImmutableValueMap getValueMap() { return valueMap; } /** * Returns the typed values for the given {@link AttributeDefinition} as an {@link ImmutableSet} or returns {@code * null} if no attribute in this map matches the given {@link AttributeDefinition}. * * @param attributeDefinition the attribute definition to look up. * @return The values (if any) corresponding to the given {@link AttributeDefinition}. */ @Nullable public <T> ImmutableSet<? extends T> getValues(@Nonnull AttributeDefinition<T> attributeDefinition) { return valueMap.getValues(attributeDefinition); } /** * Returns the sub-map of this map where all the keys have the given friendly name or returns {@code null} if no * attribute in this map has that friendly name. * <p> * The returned map has {@link AttributeDefinition} as Map keys and {@link ImmutableSet} of typed values matching * the types of the definition as Map values. * * @param friendlyName the friendly name which returned attribute keys must possess * @return a sub-map of this map where the keys are the {@link AttributeDefinition}s which have the given friendly * name and where the values are {@link ImmutableSet}s of typed values or returns {@code null} if no attribute in * this map has that friendly name. */ @Nullable public ImmutableValueMap getValuesByFriendlyName(@Nonnull String friendlyName) { return valueMap.getValuesByFriendlyName(friendlyName); } @Nullable public <T> ImmutableSet<? extends T> getValuesByNameUri(@Nonnull String name) { return valueMap.getValuesByNameUri(name); } @Nullable public <T> ImmutableSet<? extends T> getValuesByNameUri(@Nonnull URI name) { return valueMap.getValuesByNameUri(name); } public boolean isEmpty() { return attributeMap.isEmpty(); } public int size() { return attributeMap.size(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ImmutableAttributeMap that = (ImmutableAttributeMap) o; return null != attributeMap ? attributeMap.equals(that.attributeMap) : that.attributeMap == null; } @Override public int hashCode() { return null != attributeMap ? attributeMap.hashCode() : 0; } @Override public String toString() { return null != attributeMap ? attributeMap.toString() : ""; } // The declarations below are necessary for serialization /** * Effective Java, 2nd Ed. : Item 78: Serialization Proxy pattern. */ private void readObject(@Nonnull ObjectInputStream objectInputStream) throws InvalidObjectException { throw new InvalidObjectException("Serialization Proxy required"); } /** * Effective Java, 2nd Ed. : Item 78: Serialization Proxy pattern. */ private void writeObject(@Nonnull ObjectOutputStream out) throws InvalidObjectException { throw new InvalidObjectException("Serialization Proxy required"); } /** * Effective Java, 2nd Ed. : Item 78: Serialization Proxy pattern. */ private Object writeReplace() throws ObjectStreamException { return new SerializationProxy(this); } }