001/*
002 * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Bogdan Stefanescu
018 *     Florent Guillaume
019 */
020package org.nuxeo.ecm.core.api.model.impl;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029import java.util.ListIterator;
030
031import org.nuxeo.common.collections.PrimitiveArrays;
032import org.nuxeo.ecm.core.api.ListDiff;
033import org.nuxeo.ecm.core.api.PropertyException;
034import org.nuxeo.ecm.core.api.model.InvalidPropertyValueException;
035import org.nuxeo.ecm.core.api.model.Property;
036import org.nuxeo.ecm.core.api.model.PropertyConversionException;
037import org.nuxeo.ecm.core.api.model.PropertyVisitor;
038import org.nuxeo.ecm.core.api.model.ReadOnlyPropertyException;
039import org.nuxeo.ecm.core.schema.types.Field;
040import org.nuxeo.ecm.core.schema.types.ListType;
041
042public class ListProperty extends AbstractProperty implements List<Property> {
043
044    private static final long serialVersionUID = 1L;
045
046    /**
047     * The corresponding field.
048     */
049    protected final Field field;
050
051    protected final List<Property> children;
052
053    public ListProperty(Property parent, Field field) {
054        super(parent);
055        this.field = field;
056        children = new ArrayList<Property>();
057    }
058
059    public ListProperty(Property parent, Field field, int flags) {
060        super(parent, flags);
061        this.field = field;
062        children = new ArrayList<Property>();
063    }
064
065    @Override
066    public void internalSetValue(Serializable value) throws PropertyException {
067    }
068
069    /**
070     * TODO FIXME XXX uncommented <code>return true;</code> see NXP-1653.
071     *
072     * @see DefaultPropertyFactory line 216
073     * @see {@link ListProperty#getValue}
074     * @see {@link ListProperty#accept}
075     */
076    @Override
077    public boolean isContainer() {
078        // return true; // - this can be uncommented when scalar list will be
079        // fixed
080        return !getType().isScalarList();
081    }
082
083    @Override
084    public Property addValue(int index, Object value) throws PropertyException {
085        Field lfield = getType().getField();
086        Property property = getRoot().createProperty(this, lfield, IS_NEW);
087        property.setValue(value);
088        children.add(index, property);
089        return property;
090    }
091
092    @Override
093    public Property addValue(Object value) throws PropertyException {
094        Field lfield = getType().getField();
095        Property property = getRoot().createProperty(this, lfield, IS_NEW);
096        property.setValue(value);
097        children.add(property);
098        return property;
099    }
100
101    @Override
102    public Property addEmpty() {
103        Field lfield = getType().getField();
104        Property property = getRoot().createProperty(this, lfield, 0);
105        children.add(property);
106        return property;
107    }
108
109    @Override
110    public Collection<Property> getChildren() {
111        return Collections.unmodifiableCollection(children);
112    }
113
114    @Override
115    public String getName() {
116        return field.getName().getPrefixedName();
117    }
118
119    @Override
120    public ListType getType() {
121        return (ListType) field.getType();
122    }
123
124    @Override
125    public Property get(String name) {
126        try {
127            return get(Integer.parseInt(name));
128        } catch (NumberFormatException e) {
129            return null;
130        }
131    }
132
133    @Override
134    public Property get(int index) {
135        try {
136            return children.get(index);
137        } catch (IndexOutOfBoundsException e) {
138            return null;
139        }
140    }
141
142    @Override
143    protected Serializable getDefaultValue() {
144        Serializable value = (Serializable) field.getDefaultValue();
145        if (value == null) {
146            value = new ArrayList<Serializable>();
147        }
148        return value;
149    }
150
151    @Override
152    public Serializable internalGetValue() throws PropertyException {
153        if (children.isEmpty()) {
154            return new ArrayList<String>();
155        }
156        // noinspection CollectionDeclaredAsConcreteClass
157        ArrayList<Object> list = new ArrayList<Object>(children.size());
158        for (Property property : children) {
159            list.add(property.getValue());
160        }
161        // TODO XXX FIXME for compatibility - this treats sclar lists as array
162        // see NXP-1653. remove this when will be fixed
163        // see also isContainer(), setValue() and accept()
164        // if (getType().isScalarList()) {
165        // if (list.isEmpty()) return null;
166        // Object o = list.get(0);
167        // Class<?> type = o.getClass();
168        // if (o == null) {
169        // // don't know the class of the element
170        // // try to use schema information
171        // type = JavaTypes.getPrimitiveClass(getType().getFieldType());
172        // if (type == null) { // this should be a bug
173        // throw new IllegalStateException("Scalar list type is not known - this
174        // should be a bug");
175        // }
176        // } else {
177        // type = o.getClass();
178        // }
179        // return list.toArray((Object[])Array.newInstance(type, list.size()));
180        // }
181        // end of compatibility code <--------
182        return list;
183    }
184
185    @Override
186    public Serializable getValueForWrite() throws PropertyException {
187        if (isPhantom() || isRemoved()) {
188            return getDefaultValue();
189        }
190        if (children.isEmpty()) {
191            return new ArrayList<String>();
192        }
193        // noinspection CollectionDeclaredAsConcreteClass
194        ArrayList<Object> list = new ArrayList<Object>(children.size());
195        for (Property property : children) {
196            list.add(property.getValueForWrite());
197        }
198        return list;
199    }
200
201    @Override
202    @SuppressWarnings("unchecked")
203    public void init(Serializable value) throws PropertyException {
204        if (value == null) { // IGNORE null values - properties will be
205                             // considered PHANTOMS
206            return;
207        }
208        List<Serializable> list;
209        if (value.getClass().isArray()) { // accept also arrays
210            list = (List<Serializable>) PrimitiveArrays.toList(value);
211        } else {
212            list = (List<Serializable>) value;
213        }
214        children.clear(); // do not use clear() method since it is marking the
215                          // list as dirty
216        Field lfield = getType().getField();
217        for (Serializable obj : list) {
218            Property property = getRoot().createProperty(this, lfield, 0);
219            property.init(obj);
220            children.add(property);
221        }
222        removePhantomFlag();
223    }
224
225    @Override
226    public void setValue(Object value) throws PropertyException {
227        if (isReadOnly()) {
228            throw new ReadOnlyPropertyException(getPath());
229        }
230        if (value == null) {
231            List<Property> temp = new ArrayList<Property>(children);
232            for (Property p : temp) { // remove all children
233                p.remove();
234            }
235            return;
236        }
237        Collection<?> col;
238        Class<?> klass = value.getClass();
239        if (klass == ListDiff.class) { // listdiff support for compatibility
240            applyListDiff((ListDiff) value);
241            return;
242        } else if (klass.isArray()) { // array support
243            col = arrayToList(value);
244        } else if (value instanceof Collection) { // collection support
245            col = (Collection<?>) value;
246        } else {
247            throw new InvalidPropertyValueException(getPath());
248        }
249        clear();
250        Field lfield = getType().getField();
251        for (Object obj : col) {
252            Property property = getRoot().createProperty(this, lfield, IS_NEW);
253            property.setValue(obj);
254            children.add(property);
255        }
256    }
257
258    @Override
259    public void clear() {
260        children.clear();
261        setIsModified();
262    }
263
264    @Override
265    public Field getField() {
266        return field;
267    }
268
269    public boolean remove(Property property) {
270        if (!children.remove(property)) { // physically remove the property
271            return false; // no such item
272        }
273        setIsModified();
274        return true;
275    }
276
277    @Override
278    public Property remove(int index) {
279        Property property = children.remove(index);
280        setIsModified();
281        return property;
282    }
283
284    @Override
285    public Object clone() throws CloneNotSupportedException {
286        ListProperty clone = (ListProperty) super.clone();
287        return clone;
288    }
289
290    @Override
291    public void accept(PropertyVisitor visitor, Object arg) throws PropertyException {
292        arg = visitor.visit(this, arg);
293        if (arg != null && isContainer()) {
294            for (Property property : children) {
295                property.accept(visitor, arg);
296            }
297        }
298    }
299
300    /* ---------------------------- type conversion ------------------------ */
301
302    @Override
303    public boolean isNormalized(Object value) {
304        return value == null || (value instanceof Collection && value instanceof Serializable);
305    }
306
307    @Override
308    public Serializable normalize(Object value) throws PropertyConversionException {
309        if (isNormalized(value)) {
310            return (Serializable) value;
311        }
312        if (value.getClass().isArray()) {
313            return arrayToList(value);
314        }
315        throw new PropertyConversionException(value.getClass(), List.class);
316    }
317
318    @SuppressWarnings("unchecked")
319    @Override
320    public <T> T convertTo(Serializable value, Class<T> toType) throws PropertyConversionException {
321        if (value == null) {
322            return null;
323        } else if (toType.isAssignableFrom(value.getClass())) {
324            return toType.cast(value);
325        }
326        if (toType.isArray()) {
327            return (T) ((Collection<?>) value).toArray();
328        } else if (toType == List.class || toType == Collection.class) {
329            // TODO we need this for compatibility with scalar lists
330            if (value.getClass().isArray()) {
331                if (value.getClass().isPrimitive()) {
332                    return (T) PrimitiveArrays.toList(value);
333                } else {
334                    return (T) Arrays.asList((Object[]) value);
335                }
336            }
337        }
338        return super.convertTo(value, toType);
339    }
340
341    // Must return ArrayList
342    public static ArrayList<?> arrayToList(Object obj) {
343        Object[] ar = PrimitiveArrays.toObjectArray(obj);
344        ArrayList<Object> list = new ArrayList<Object>(ar.length);
345        list.addAll(Arrays.asList(ar));
346        return list;
347    }
348
349    /**
350     * Supports ListDiff for compatibility.
351     *
352     * @param ld
353     */
354    public void applyListDiff(ListDiff ld) throws PropertyException {
355        for (ListDiff.Entry entry : ld.diff()) {
356            switch (entry.type) {
357            case ListDiff.ADD:
358                addValue(entry.value);
359                break;
360            case ListDiff.INSERT:
361                addValue(entry.index, entry.value);
362                break;
363            case ListDiff.REMOVE:
364                remove(entry.index);
365                break;
366            case ListDiff.CLEAR:
367                clear();
368                break;
369            case ListDiff.MODIFY:
370                get(entry.index).setValue(entry.value);
371                break;
372            case ListDiff.MOVE:
373                int toIndex = (Integer) entry.value;
374                int fromIndex = entry.index;
375                Property src = children.get(fromIndex);
376                src.moveTo(toIndex);
377                break;
378            }
379        }
380    }
381
382    @Override
383    public boolean isSameAs(Property property) throws PropertyException {
384        if (!(property instanceof ListProperty)) {
385            return false;
386        }
387        ListProperty lp = (ListProperty) property;
388        List<Property> c1 = children;
389        List<Property> c2 = lp.children;
390        if (c1.size() != c2.size()) {
391            return false;
392        }
393        for (int i = 0, size = c1.size(); i < size; i++) {
394            Property p1 = c1.get(i);
395            Property p2 = c2.get(i);
396            if (!p1.isSameAs(p2)) {
397                return false;
398            }
399        }
400        return true;
401    }
402
403    @Override
404    public Iterator<Property> getDirtyChildren() {
405        if (!isContainer()) {
406            throw new UnsupportedOperationException("Cannot iterate over children of scalar properties");
407        }
408        return new DirtyPropertyIterator(children.iterator());
409    }
410
411    public int indexOf(Property property) {
412        for (int i = 0, size = children.size(); i < size; i++) {
413            Property p = children.get(i);
414            if (p == property) {
415                return i;
416            }
417        }
418        return -1;
419    }
420
421    boolean moveTo(Property property, int index) {
422        if (index < 0 || index > children.size()) {
423            throw new IndexOutOfBoundsException("Index out of bounds: " + index + ". Bounds are: 0 - "
424                    + children.size());
425        }
426        int i = indexOf(property);
427        if (i == -1) {
428            throw new UnsupportedOperationException("You are trying to move a property that is not part of a list");
429        }
430        if (i == index) {
431            return false;
432        }
433        if (i < index) {
434            children.add(index + 1, property);
435            children.remove(i);
436        } else {
437            children.add(index, property);
438            children.remove(i + 1);
439        }
440        // new property must be dirty
441        for (int j = Math.min(index, i); j < children.size(); j++) {
442            ((AbstractProperty) children.get(j)).setIsModified();
443        }
444        return true;
445    }
446
447    /**
448     * Throws UnsupportedOperationException, added to implement List<Property> interface
449     */
450    @Override
451    public void add(int index, Property element) {
452        throw new UnsupportedOperationException();
453    }
454
455    /**
456     * Throws UnsupportedOperationException, added to implement List<Property> interface
457     */
458    @Override
459    public boolean add(Property o) {
460        throw new UnsupportedOperationException();
461    }
462
463    /**
464     * Throws UnsupportedOperationException, added to implement List<Property> interface
465     */
466    @Override
467    public boolean addAll(Collection<? extends Property> c) {
468        throw new UnsupportedOperationException();
469    }
470
471    /**
472     * Throws UnsupportedOperationException, added to implement List<Property> interface
473     */
474    @Override
475    public boolean addAll(int index, Collection<? extends Property> c) {
476        throw new UnsupportedOperationException();
477    }
478
479    /**
480     * Throws UnsupportedOperationException, added to implement List<Property> interface
481     */
482    @Override
483    public boolean contains(Object o) {
484        throw new UnsupportedOperationException();
485    }
486
487    /**
488     * Throws UnsupportedOperationException, added to implement List<Property> interface
489     */
490    @Override
491    public boolean containsAll(Collection<?> c) {
492        throw new UnsupportedOperationException();
493    }
494
495    /**
496     * Throws UnsupportedOperationException, added to implement List<Property> interface
497     */
498    @Override
499    public int indexOf(Object o) {
500        throw new UnsupportedOperationException();
501    }
502
503    @Override
504    public boolean isEmpty() {
505        return children.isEmpty();
506    }
507
508    /**
509     * Throws UnsupportedOperationException, added to implement List<Property> interface
510     */
511    @Override
512    public int lastIndexOf(Object o) {
513        throw new UnsupportedOperationException();
514    }
515
516    /**
517     * Throws UnsupportedOperationException, added to implement List<Property> interface
518     */
519    @Override
520    public ListIterator<Property> listIterator() {
521        return children.listIterator();
522    }
523
524    /**
525     * Throws UnsupportedOperationException, added to implement List<Property> interface
526     */
527    @Override
528    public ListIterator<Property> listIterator(int index) {
529        return children.listIterator(index);
530    }
531
532    /**
533     * Throws UnsupportedOperationException, added to implement List<Property> interface
534     */
535    @Override
536    public boolean remove(Object o) {
537        throw new UnsupportedOperationException();
538    }
539
540    /**
541     * Throws UnsupportedOperationException, added to implement List<Property> interface
542     */
543    @Override
544    public boolean removeAll(Collection<?> c) {
545        throw new UnsupportedOperationException();
546    }
547
548    /**
549     * Throws UnsupportedOperationException, added to implement List<Property> interface
550     */
551    @Override
552    public boolean retainAll(Collection<?> c) {
553        throw new UnsupportedOperationException();
554    }
555
556    /**
557     * Throws UnsupportedOperationException, added to implement List<Property> interface
558     */
559    @Override
560    public Property set(int index, Property element) {
561        throw new UnsupportedOperationException();
562    }
563
564    /**
565     * Throws UnsupportedOperationException, added to implement List<Property> interface
566     */
567    @Override
568    public List<Property> subList(int fromIndex, int toIndex) {
569        throw new UnsupportedOperationException();
570    }
571
572    @Override
573    public Object[] toArray() {
574        return children.toArray();
575    }
576
577    @Override
578    public <T> T[] toArray(T[] a) {
579        return children.toArray(a);
580    }
581
582    @Override
583    public void clearDirtyFlags() {
584        // even makes child properties not dirty
585        super.clearDirtyFlags();
586        for (Property child : children) {
587            child.clearDirtyFlags();
588        }
589    }
590
591}