/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nuxeo - initial API and implementation * * $Id$ */ package org.eclipse.ecr.core.api.model.impl; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import org.eclipse.ecr.core.api.ListDiff; import org.eclipse.ecr.core.api.model.InvalidPropertyValueException; import org.eclipse.ecr.core.api.model.Property; import org.eclipse.ecr.core.api.model.PropertyConversionException; import org.eclipse.ecr.core.api.model.PropertyException; import org.eclipse.ecr.core.api.model.PropertyVisitor; import org.eclipse.ecr.core.api.model.ReadOnlyPropertyException; import org.eclipse.ecr.core.schema.types.Field; import org.eclipse.ecr.core.schema.types.ListType; import org.nuxeo.common.collections.PrimitiveArrays; /** * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> * */ public class ListProperty extends AbstractProperty implements List<Property> { private static final long serialVersionUID = 2050498811068422820L; /** * The corresponding field. */ protected final Field field; protected final List<Property> children; public ListProperty(Property parent, Field field) { super(parent); this.field = field; children = new ArrayList<Property>(); } public ListProperty(Property parent, Field field, int flags) { super(parent, flags); this.field = field; children = new ArrayList<Property>(); } @Override public void internalSetValue(Serializable value) throws PropertyException { } /** * TODO FIXME XXX uncommented <code>return true;</code> see NXP-1653. * * @see DefaultPropertyFactory line 216 * @see {@link ListProperty#getValue} * @see {@link ListProperty#accept} */ @Override public boolean isContainer() { // return true; // - this can be uncommented when scalar list will be // fixed return !getType().isScalarList(); } @Override public Property addValue(int index, Object value) throws PropertyException { Field lfield = getType().getField(); Property property = getRoot().createProperty(this, lfield, IS_NEW); property.setValue(value); children.add(index, property); return property; } @Override public Property addValue(Object value) throws PropertyException { Field lfield = getType().getField(); Property property = getRoot().createProperty(this, lfield, IS_NEW); property.setValue(value); children.add(property); return property; } @Override public Property addEmpty() { Field lfield = getType().getField(); Property property = getRoot().createProperty(this, lfield, 0); children.add(property); return property; } @Override public Collection<Property> getChildren() { return Collections.unmodifiableCollection(children); } @Override public String getName() { return field.getName().getPrefixedName(); } @Override public ListType getType() { return (ListType) field.getType(); } @Override public Property get(String name) { return children.get(Integer.parseInt(name)); } @Override public Property get(int index) { return children.get(index); } @Override protected Serializable getDefaultValue() { Serializable value = (Serializable) field.getDefaultValue(); if (value == null) { value = new ArrayList<Serializable>(); } return value; } @Override public Serializable internalGetValue() throws PropertyException { if (children.isEmpty()) { return new ArrayList<String>(); } // noinspection CollectionDeclaredAsConcreteClass ArrayList<Object> list = new ArrayList<Object>(children.size()); for (Property property : children) { list.add(property.getValue()); } // TODO XXX FIXME for compatibility - this treats sclar lists as array // see NXP-1653. remove this when will be fixed // see also isContainer(), setValue() and accept() // if (getType().isScalarList()) { // if (list.isEmpty()) return null; // Object o = list.get(0); // Class<?> type = o.getClass(); // if (o == null) { // // don't know the class of the element // // try to use schema information // type = JavaTypes.getPrimitiveClass(getType().getFieldType()); // if (type == null) { // this should be a bug // throw new IllegalStateException("Scalar list type is not known - this // should be a bug"); // } // } else { // type = o.getClass(); // } // return list.toArray((Object[])Array.newInstance(type, list.size())); // } // end of compatibility code <-------- return list; } @Override public Serializable getValueForWrite() throws PropertyException { if (isPhantom() || isRemoved()) { return getDefaultValue(); } if (children.isEmpty()) { return new ArrayList<String>(); } // noinspection CollectionDeclaredAsConcreteClass ArrayList<Object> list = new ArrayList<Object>(children.size()); for (Property property : children) { list.add(property.getValueForWrite()); } return list; } @Override @SuppressWarnings("unchecked") public void init(Serializable value) throws PropertyException { if (value == null) { // IGNORE null values - properties will be // considered PHANTOMS return; } List<Serializable> list; if (value.getClass().isArray()) { // accept also arrays list = (List<Serializable>) PrimitiveArrays.toList(value); } else { list = (List<Serializable>) value; } children.clear(); // do not use clear() method since it is marking the // list as dirty Field lfield = getType().getField(); for (Serializable obj : list) { Property property = getRoot().createProperty(this, lfield, isValidating() ? IS_VALIDATING : 0); property.init(obj); children.add(property); } removePhantomFlag(); } @Override public void setValue(Object value) throws PropertyException { if (isReadOnly()) { throw new ReadOnlyPropertyException(getPath()); } if (value == null) { List<Property> temp = new ArrayList<Property>(children); for (Property p : temp) { // remove all children p.remove(); } return; } Collection<?> col; Class<?> klass = value.getClass(); if (klass == ListDiff.class) { // listdiff support for compatibility applyListDiff((ListDiff) value); return; } else if (klass.isArray()) { // array support col = arrayToList(value); } else if (value instanceof Collection) { // collection support col = (Collection<?>) value; } else { throw new InvalidPropertyValueException(getPath()); } clear(); Field lfield = getType().getField(); for (Object obj : col) { Property property = getRoot().createProperty(this, lfield, IS_NEW); property.setValue(obj); children.add(property); } } @Override public void clear() { while (!children.isEmpty()) { Property property = children.remove(0); markChildAsRemoved(property); } setIsModified(); } @Override public Field getField() { return field; } public boolean remove(Property property) { if (!children.remove(property)) { // physically remove the property return false; // no such item } // mark the property as removed using the "@removed" application data markChildAsRemoved(property); setIsModified(); return true; } @Override public Property remove(int index) { Property property = children.remove(index); // mark the property as removed using the "@removed" application data markChildAsRemoved(property); setIsModified(); return property; } @SuppressWarnings("unchecked") private void markChildAsRemoved(Property property) { List<String> removed = (List<String>) getData("@removed"); if (removed == null) { removed = new ArrayList<String>(); setData("@removed", removed); } // use the key as the property data (this is the internal name of // the list item property) removed.add((String) property.getData()); } @Override public Object clone() throws CloneNotSupportedException { ListProperty clone = (ListProperty) super.clone(); return clone; } @Override public void accept(PropertyVisitor visitor, Object arg) throws PropertyException { arg = visitor.visit(this, arg); if (arg != null && isContainer()) { for (Property property : children) { property.accept(visitor, arg); } } } /* ---------------------------- type conversion ------------------------ */ @Override public boolean isNormalized(Object value) { return value == null || (value instanceof Collection && value instanceof Serializable); } @Override public Serializable normalize(Object value) throws PropertyConversionException { if (isNormalized(value)) { return (Serializable) value; } if (value.getClass().isArray()) { return arrayToList(value); } throw new PropertyConversionException(value.getClass(), List.class); } @SuppressWarnings("unchecked") @Override public <T> T convertTo(Serializable value, Class<T> toType) throws PropertyConversionException { if (value == null) { return null; } else if (toType.isAssignableFrom(value.getClass())) { return toType.cast(value); } if (toType.isArray()) { return (T) ((Collection<?>) value).toArray(); } else if (toType == List.class || toType == Collection.class) { // TODO we need this for compatibility with scalar lists if (value.getClass().isArray()) { if (value.getClass().isPrimitive()) { return (T) PrimitiveArrays.toList(value); } else { return (T) Arrays.asList((Object[]) value); } } } return super.convertTo(value, toType); } // Must return ArrayList public static ArrayList<?> arrayToList(Object obj) { Object[] ar = PrimitiveArrays.toObjectArray(obj); ArrayList<Object> list = new ArrayList<Object>(ar.length); list.addAll(Arrays.asList(ar)); return list; } /** * Supports ListDiff for compatibility. * * @param ld */ public void applyListDiff(ListDiff ld) throws PropertyException { for (ListDiff.Entry entry : ld.diff()) { switch (entry.type) { case ListDiff.ADD: addValue(entry.value); break; case ListDiff.INSERT: addValue(entry.index, entry.value); break; case ListDiff.REMOVE: remove(entry.index); break; case ListDiff.CLEAR: clear(); break; case ListDiff.MODIFY: get(entry.index).setValue(entry.value); break; case ListDiff.MOVE: int toIndex = (Integer) entry.value; int fromIndex = entry.index; Property src = children.get(fromIndex); src.moveTo(toIndex); break; } } } @Override public boolean isSameAs(Property property) throws PropertyException { if (!(property instanceof ListProperty)) { return false; } ListProperty lp = (ListProperty) property; List<Property> c1 = children; List<Property> c2 = lp.children; if (c1.size() != c2.size()) { return false; } for (int i = 0, size = c1.size(); i < size; i++) { Property p1 = c1.get(i); Property p2 = c2.get(i); if (!p1.isSameAs(p2)) { return false; } } return true; } @Override public Iterator<Property> getDirtyChildren() { if (!isContainer()) { throw new UnsupportedOperationException( "Cannot iterate over children of scalar properties"); } return new DirtyPropertyIterator(children.iterator()); } public int indexOf(Property property) { for (int i = 0, size = children.size(); i < size; i++) { Property p = children.get(i); if (p == property) { return i; } } return -1; } boolean moveTo(Property property, int index) { if (index < 0 || index > children.size()) { throw new IndexOutOfBoundsException("Index out of bounds: " + index + ". Bounds are: 0 - " + children.size()); } int i = indexOf(property); if (i == -1) { throw new UnsupportedOperationException( "You are trying to move a property that is not part of a list"); } if (i == index) { return false; } if (i < index) { children.add(index + 1, property); children.remove(i); } else { children.add(index, property); children.remove(i + 1); } return true; } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public void add(int index, Property element) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public boolean add(Property o) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public boolean addAll(Collection<? extends Property> c) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public boolean addAll(int index, Collection<? extends Property> c) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public boolean containsAll(Collection<?> c) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public int indexOf(Object o) { throw new UnsupportedOperationException(); } @Override public boolean isEmpty() { return children.isEmpty(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public int lastIndexOf(Object o) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public ListIterator<Property> listIterator() { return children.listIterator(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public ListIterator<Property> listIterator(int index) { return children.listIterator(index); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public Property set(int index, Property element) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException, added to implement List<Property> * interface */ @Override public List<Property> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } @Override public Object[] toArray() { return children.toArray(); } @Override public <T> T[] toArray(T[] a) { return children.toArray(a); } }