001/*
002 * (C) Copyright 2006-2016 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.ArrayList;
027import java.util.Arrays;
028import java.util.Collection;
029
030import org.nuxeo.common.collections.PrimitiveArrays;
031import org.nuxeo.ecm.core.api.PropertyException;
032import org.nuxeo.ecm.core.api.model.Property;
033import org.nuxeo.ecm.core.api.model.PropertyConversionException;
034import org.nuxeo.ecm.core.schema.types.Field;
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(null);
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]; // NOSONAR
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 (value == null) {
104            return null;
105        } else if (value.getClass().isArray()) {
106            return convert(Arrays.asList((Object[]) value));
107        } else if (value instanceof Collection) {
108            return convert((Collection<?>) value);
109        }
110        throw new PropertyConversionException(value.getClass(), Object[].class, getXPath());
111    }
112
113    protected Serializable convert(Collection<?> value) throws PropertyConversionException {
114        Property typedProperty = getRoot().createProperty(null, getType().getField(), NONE);
115        Collection<Object> col = new ArrayList<>(value.size());
116        for (Object v : value) {
117            if (v == null) {
118                col.add(null);
119            } else {
120                col.add(typedProperty.normalize(v));
121            }
122        }
123        return col.toArray((Object[]) Array.newInstance(typedProperty.newInstance().getClass(), col.size()));
124    }
125
126    @SuppressWarnings("unchecked")
127    @Override
128    public <T> T convertTo(Serializable value, Class<T> toType) throws PropertyConversionException {
129        if (toType.isArray()) {
130            return (T) PrimitiveArrays.toObjectArray(value);
131        } else if (Collection.class.isAssignableFrom(toType)) {
132            return (T) Arrays.asList((Object[]) value);
133        }
134        throw new PropertyConversionException(value.getClass(), toType);
135    }
136
137    @Override
138    public Object newInstance() {
139        return new Serializable[0];
140    }
141
142    // this boolean array managed the dirty flags for arrayproperty childs
143    private boolean[] childDirty = null;
144
145    protected boolean[] getChildDirty() {
146        if (childDirty == null) {
147            Object[] oldValues = (Object[]) internalGetValue();
148            if (oldValues == null) {
149                childDirty = new boolean[0];
150            } else {
151                childDirty = new boolean[oldValues.length];
152                for (int i = 0; i < childDirty.length; i++) {
153                    childDirty[i] = false;
154                }
155            }
156        }
157        return childDirty;
158    }
159
160    /**
161     * This method provides a way to know if some arrayproperty values are dirty: value or index changed. since 7.2
162     */
163    public boolean isDirty(int index) {
164        if (index > getChildDirty().length) {
165            throw new IndexOutOfBoundsException(
166                    "Index out of bounds: " + index + ". Bounds are: 0 - " + (getChildDirty().length - 1));
167        }
168        return getChildDirty()[index];
169    }
170
171    @Override
172    public void clearDirtyFlags() {
173        // even makes child properties not dirty
174        super.clearDirtyFlags();
175        for (int i = 0; i < getChildDirty().length; i++) {
176            childDirty[i] = false;
177        }
178    }
179
180}