001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 *
012 * $Id$
013 */
014
015package org.nuxeo.ecm.core.api.model.impl;
016
017import java.io.Serializable;
018import java.lang.reflect.Array;
019import java.util.Arrays;
020import java.util.Collection;
021
022import org.nuxeo.common.collections.PrimitiveArrays;
023import org.nuxeo.ecm.core.api.PropertyException;
024import org.nuxeo.ecm.core.api.model.Property;
025import org.nuxeo.ecm.core.api.model.PropertyConversionException;
026import org.nuxeo.ecm.core.schema.types.Field;
027import org.nuxeo.ecm.core.schema.types.JavaTypes;
028import org.nuxeo.ecm.core.schema.types.ListType;
029
030/**
031 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
032 */
033public class ArrayProperty extends ScalarProperty {
034
035    private static final long serialVersionUID = 0L;
036
037    public ArrayProperty(Property parent, Field field, int flags) {
038        super(parent, field, flags);
039    }
040
041    @Override
042    public ListType getType() {
043        return (ListType) super.getType();
044    }
045
046    @Override
047    public boolean isContainer() {
048        return false;
049    }
050
051    @Override
052    public void setValue(Object value) throws PropertyException {
053        // this code manage dirty status for the arrayproperty and its childs values
054        // it checks whether the property changed, or their index changed
055        if (value == null) {
056            childDirty = new boolean[0];
057            super.setValue(value);
058        } else {
059            Object[] oldValues = (Object[]) internalGetValue();
060            boolean[] oldChildDirty = getChildDirty();
061            super.setValue(value);
062            Object[] newValues = (Object[]) internalGetValue();
063            boolean[] newChildDirty = new boolean[newValues != null ? newValues.length : 0];
064            for (int i = 0; i < newChildDirty.length; i++) {
065                Object newValue = newValues[i];
066                if (oldValues == null || i >= oldValues.length) {
067                    newChildDirty[i] = true;
068                } else {
069                    Object oldValue = oldValues[i];
070                    if (!((newValue == null && oldValue == null) || (newValue != null && newValue.equals(oldValue)))) {
071                        newChildDirty[i] = true;
072                    } else {
073                        newChildDirty[i] = false || oldChildDirty[i];
074                    }
075                }
076            }
077            childDirty = newChildDirty;
078        }
079    }
080
081    @Override
082    protected boolean isSameValue(Serializable value1, Serializable value2) {
083        Object[] castedtValue1 = (Object[]) value1;
084        Object[] castedtValue2 = (Object[]) value2;
085        return castedtValue1 == castedtValue2 || (castedtValue1 == null && castedtValue2.length == 0)
086                || (castedtValue2 == null && castedtValue1.length == 0) || Arrays.equals(castedtValue1, castedtValue2);
087    }
088
089    @Override
090    public boolean isNormalized(Object value) {
091        return value == null || value.getClass().isArray();
092    }
093
094    @Override
095    public Serializable normalize(Object value) throws PropertyConversionException {
096        if (isNormalized(value)) {
097            return (Serializable) value;
098        }
099        if (value instanceof Collection) {
100            Collection<?> col = (Collection<?>) value;
101            Class<?> klass = JavaTypes.getClass(getType().getFieldType());
102            return col.toArray((Object[]) Array.newInstance(klass, col.size()));
103        }
104        throw new PropertyConversionException(value.getClass(), Object[].class, getPath());
105    }
106
107    @SuppressWarnings("unchecked")
108    @Override
109    public <T> T convertTo(Serializable value, Class<T> toType) throws PropertyConversionException {
110        if (toType.isArray()) {
111            return (T) PrimitiveArrays.toObjectArray(value);
112        } else if (Collection.class.isAssignableFrom(toType)) {
113            return (T) Arrays.asList((Object[]) value);
114        }
115        throw new PropertyConversionException(value.getClass(), toType);
116    }
117
118    @Override
119    public Object newInstance() {
120        return new Serializable[0];
121    }
122
123    // this boolean array managed the dirty flags for arrayproperty childs
124    private boolean[] childDirty = null;
125
126    protected boolean[] getChildDirty() {
127        if (childDirty == null) {
128            Object[] oldValues = (Object[]) internalGetValue();
129            if (oldValues == null) {
130                childDirty = new boolean[0];
131            } else {
132                childDirty = new boolean[oldValues.length];
133                for (int i = 0; i < childDirty.length; i++) {
134                    childDirty[i] = false;
135                }
136            }
137        }
138        return childDirty;
139    }
140
141    /**
142     * This method provides a way to know if some arrayproperty values are dirty: value or index changed. since 7.2
143     */
144    public boolean isDirty(int index) {
145        if (index > getChildDirty().length) {
146            throw new IndexOutOfBoundsException("Index out of bounds: " + index + ". Bounds are: 0 - "
147                    + (getChildDirty().length - 1));
148        }
149        return getChildDirty()[index];
150    }
151
152    @Override
153    public void clearDirtyFlags() {
154        // even makes child properties not dirty
155        super.clearDirtyFlags();
156        for (int i = 0; i < getChildDirty().length; i++) {
157            childDirty[i] = false;
158        }
159    }
160
161}