/** * Creates a new association of the given role. * * @param role Information about the association. */ public MultiValuedAssociation(final DefaultAssociationRole role) { super(role); values = new CheckedArrayList<>(AbstractFeature.class); }
/** * Returns the given collection as a {@code CheckedArrayList} instance of the given element type. * * @param <E> the element type. * @param collection the collection or {@code null}. * @param type the element type. * @return the given collection as a {@code CheckedArrayList}, or {@code null} if the given collection was null. * @throws ClassCastException if an element is not of the expected type. * * @since 0.5 */ @SuppressWarnings("unchecked") public static <E> CheckedArrayList<E> castOrCopy(final Collection<?> collection, final Class<E> type) { if (collection == null) { return null; } if (collection instanceof CheckedArrayList<?> && ((CheckedArrayList<?>) collection).type == type) { return (CheckedArrayList<E>) collection; } else { final CheckedArrayList<E> list = new CheckedArrayList<>(type, collection.size()); list.addAll((Collection) collection); // addAll will perform the type checks. return list; } }
/** * Creates a collection which will initially contain only the given value. * At the difference of {@link Collections#singletonList(Object)}, this method returns a modifiable list. */ @SuppressWarnings("unchecked") private static <V> Collection<V> singletonList(final Class<V> valueClass, final int minimumOccurs, final Object value) { final CheckedArrayList<V> values = new CheckedArrayList<>(valueClass, Math.max(minimumOccurs, 4)); values.add((V) value); // Type will be checked by CheckedArrayList. return values; }
/** * Creates a new association of the given role initialized to the given values. * * @param role Information about the association. * @param values The initial values, or {@code null} for initializing to an empty list. */ MultiValuedAssociation(final DefaultAssociationRole role, final Object values) { super(role); if (values == null) { this.values = new CheckedArrayList<>(AbstractFeature.class); } else { this.values = CheckedArrayList.castOrCopy((CheckedArrayList<?>) values, AbstractFeature.class); } }
/** * Tests {@link CheckedArrayList#addAll(Collection)}. */ @Test public void testAddAll() { final CheckedArrayList<String> list = new CheckedArrayList<>(String.class); assertTrue(list.add("One")); assertTrue(list.addAll(Arrays.asList("Two", "Three"))); assertEquals(Arrays.asList("One", "Two", "Three"), list); }
/** * Ensures that we can not element of the wrong type in a sublist. */ @Test @DependsOnMethod("testAddWrongType") public void testAddWrongTypeToSublist() { final CheckedArrayList<String> list = new CheckedArrayList<>(String.class); assertTrue(list.add("One")); assertTrue(list.add("Two")); assertTrue(list.add("Three")); testAddWrongType(list.subList(1, 3)); // Exception message is JDK-dependent, so we can not test it. }
/** * Returns the feature, or {@code null} if none. * * @return the feature (may be {@code null}). * @throws IllegalStateException if this association contains more than one value. */ @Override public AbstractFeature getValue() { switch (values.size()) { case 0: return null; case 1: return values.get(0); default: throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, getName())); } }
/** * Sets the attribute values. All previous values are replaced by the given collection. * * @param newValues the new values. */ @Override public void setValues(final Collection<? extends V> newValues) { if (newValues != values) { ArgumentChecks.ensureNonNull("values", newValues); // The parameter name in public API is "values". values.clear(); values.addAll(newValues); } }
/** * Replaces the element at the specified position in this list with the specified element. * * @param index index of element to replace. * @param element element to be stored at the specified position. * @return the element previously at the specified position. * @throws IndexOutOfBoundsException if index out of range. * @throws NullPointerException if the given element is {@code null}. * @throws ClassCastException if the given element is not of the expected type. */ @Override public E set(final int index, final E element) { if (ensureValid(element)) { return super.set(index, element); } return get(index); }
/** * Verifies the validity of the given attribute value, and returns the value to store in the feature. * An attribute: * <ul> * <li>May be a singleton, in which case the value class is verified.</li> * <li>May be a collection, in which case the class each elements in the collection is verified.</li> * </ul> * * @param value the value, which shall be non-null. */ private static <T> Object verifyAttributeValue(final DefaultAttributeType<T> type, final Object value) { final Class<T> valueClass = type.getValueClass(); final boolean isSingleton = Field.isSingleton(type.getMaximumOccurs()); if (valueClass.isInstance(value)) { return isSingleton ? value : singletonList(valueClass, type.getMinimumOccurs(), value); } else if (!isSingleton && value instanceof Collection<?>) { return CheckedArrayList.castOrCopy((Collection<?>) value, valueClass); } else { throw new ClassCastException(illegalValueClass(type, valueClass, value)); } }
/** * Tests {@link CheckedArrayList#castOrCopy(Collection, Class)}. */ @Test @DependsOnMethod("testAddAll") public void testCastOrCopy() { assertNull(CheckedArrayList.castOrCopy(null, String.class)); final List<String> fruits = Arrays.asList("Apple", "Orange", "Raisin"); final CheckedArrayList<String> asStrings = CheckedArrayList.castOrCopy(fruits, String.class); assertEquals ("Should have the given element type.", String.class, asStrings.getElementType()); assertNotSame("Should have created a new instance.", fruits, asStrings); assertEquals ("Should contain the same data.", fruits, asStrings); assertSame ("Should cast existing instance.", asStrings, CheckedArrayList.castOrCopy(asStrings, String.class)); final CheckedArrayList<CharSequence> asChars = CheckedArrayList.castOrCopy(asStrings, CharSequence.class); assertEquals ("Should have the given element type.", CharSequence.class, asChars.getElementType()); assertNotSame("Should have created a new instance.", asStrings, asChars); assertEquals ("Should contain the same data.", asStrings, asChars); assertEquals ("Should contain the same data.", fruits, asChars); try { CheckedArrayList.castOrCopy(asChars, Integer.class); fail("Should not be allowed to cast String to Integer."); } catch (ClassCastException e) { final String message = e.getMessage(); assertTrue(message, message.contains("String")); assertTrue(message, message.contains("Integer")); } } }
/** * Returns a copy of this association. * This implementation returns a <em>shallow</em> copy: * the association {@linkplain #getValues() values} are <strong>not</strong> cloned. * * @return a clone of this association. * @throws CloneNotSupportedException if this association can not be cloned. */ @Override @SuppressWarnings("unchecked") public MultiValuedAssociation clone() throws CloneNotSupportedException { final MultiValuedAssociation clone = (MultiValuedAssociation) super.clone(); clone.values = (CheckedArrayList<AbstractFeature>) clone.values.clone(); return clone; }
/** * Creates a new association of the given role initialized to the given values. * * @param role Information about the association. * @param values The initial values, or {@code null} for initializing to an empty list. */ MultiValuedAssociation(final DefaultAssociationRole role, final Object values) { super(role); if (values == null) { this.values = new CheckedArrayList<>(AbstractFeature.class); } else { this.values = CheckedArrayList.castOrCopy((CheckedArrayList<?>) values, AbstractFeature.class); } }
/** * Returns the attribute value, or {@code null} if none. * * @return the attribute value (may be {@code null}). * @throws IllegalStateException if this attribute contains more than one value. */ @Override public V getValue() { switch (values.size()) { case 0: return null; case 1: return values.get(0); default: throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, getName())); } }
/** * Sets the attribute values. All previous values are replaced by the given collection. * * @param newValues the new values. */ @Override public void setValues(final Collection<? extends V> newValues) { if (newValues != values) { ArgumentChecks.ensureNonNull("values", newValues); // The parameter name in public API is "values". values.clear(); values.addAll(newValues); } }
/** * Replaces the element at the specified position in this list with the specified element. * * @param index index of element to replace. * @param element element to be stored at the specified position. * @return the element previously at the specified position. * @throws IndexOutOfBoundsException if index out of range. * @throws NullPointerException if the given element is {@code null}. * @throws ClassCastException if the given element is not of the expected type. */ @Override public E set(final int index, final E element) { if (ensureValid(element)) { return super.set(index, element); } return get(index); }
/** * Verifies the validity of the given attribute value, and returns the value to store in the feature. * An attribute: * <ul> * <li>May be a singleton, in which case the value class is verified.</li> * <li>May be a collection, in which case the class each elements in the collection is verified.</li> * </ul> * * @param value the value, which shall be non-null. */ private static <T> Object verifyAttributeValue(final DefaultAttributeType<T> type, final Object value) { final Class<T> valueClass = type.getValueClass(); final boolean isSingleton = Field.isSingleton(type.getMaximumOccurs()); if (valueClass.isInstance(value)) { return isSingleton ? value : singletonList(valueClass, type.getMinimumOccurs(), value); } else if (!isSingleton && value instanceof Collection<?>) { return CheckedArrayList.castOrCopy((Collection<?>) value, valueClass); } else { throw new ClassCastException(illegalValueClass(type, valueClass, value)); } }
/** * Returns a copy of this association. * This implementation returns a <em>shallow</em> copy: * the association {@linkplain #getValues() values} are <strong>not</strong> cloned. * * @return a clone of this association. * @throws CloneNotSupportedException if this association can not be cloned. */ @Override @SuppressWarnings("unchecked") public MultiValuedAssociation clone() throws CloneNotSupportedException { final MultiValuedAssociation clone = (MultiValuedAssociation) super.clone(); clone.values = (CheckedArrayList<AbstractFeature>) clone.values.clone(); return clone; }