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