001/*
002 * (C) Copyright 2006-2018 Nuxeo (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.schema.types;
021
022import java.lang.reflect.Array;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Set;
028
029import org.apache.commons.lang3.StringUtils;
030import org.nuxeo.ecm.core.schema.types.constraints.Constraint;
031
032/**
033 * The implementation for a List type.
034 */
035public class ListTypeImpl extends AbstractType implements ListType {
036
037    private static final long serialVersionUID = 1L;
038
039    protected static final String DEFAULT_VALUE_SEPARATOR = " ";
040
041    protected final Type type;
042
043    protected final Field field;
044
045    // TODO: should be removed. use field.defaultvalue instead
046    protected String defaultValue;
047
048    protected int minOccurs;
049
050    protected int maxOccurs;
051
052    protected boolean isArray = false;
053
054    public ListTypeImpl(String schema, String name, Type type, String fieldName, String defaultValue, int flags,
055            Set<Constraint> constraints, int minOccurs, int maxOccurs) {
056        super(null, schema, name);
057        if (fieldName == null) {
058            isArray = true;
059            fieldName = "item";
060        }
061        this.type = type;
062        // if the list is an array, there's no field constraint (notnull)
063        Collection<Constraint> computedConstraints = isArray ? type.getConstraints() : constraints;
064        field = new FieldImpl(QName.valueOf(fieldName), this, type, defaultValue, flags, computedConstraints);
065        this.minOccurs = minOccurs;
066        this.maxOccurs = maxOccurs;
067        this.defaultValue = defaultValue;
068    }
069
070    public ListTypeImpl(String schema, String name, Type type, String fieldName, String defaultValue, int minOccurs,
071            int maxOccurs) {
072        this(schema, name, type, fieldName, defaultValue, 0, new HashSet<>(), minOccurs, maxOccurs);
073    }
074
075    public ListTypeImpl(String schema, String name, Type type) {
076        this(schema, name, type, null, null, 0, -1);
077    }
078
079    @Override
080    public void setLimits(int minOccurs, int maxOccurs) {
081        this.minOccurs = minOccurs;
082        this.maxOccurs = maxOccurs;
083    }
084
085    @Override
086    public void setDefaultValue(String value) {
087        defaultValue = value;
088    }
089
090    @Override
091    public String getFieldName() {
092        return field.getName().getLocalName();
093    }
094
095    @Override
096    public Type getFieldType() {
097        return field.getType();
098    }
099
100    @Override
101    public Field getField() {
102        return field;
103    }
104
105    @Override
106    public Object getDefaultValue() {
107        return type.decode(defaultValue);
108    }
109
110    public Type getType() {
111        return type;
112    }
113
114    @Override
115    public int getMinCount() {
116        return minOccurs;
117    }
118
119    @Override
120    public int getMaxCount() {
121        return maxOccurs;
122    }
123
124    @Override
125    public boolean isListType() {
126        return true;
127    }
128
129    @Override
130    public Object decode(String string) {
131        if (StringUtils.isBlank(string)) {
132            return null;
133        }
134        String[] split = string.split(DEFAULT_VALUE_SEPARATOR);
135        List<Object> decoded = new ArrayList<>(split.length);
136        Class<?> klass = null;
137        for (String s : split) {
138            Object o = type.decode(s);
139            if (klass == null && o != null) {
140                klass = o.getClass();
141            }
142            decoded.add(o);
143        }
144        if (klass == null) {
145            klass = Object.class;
146        }
147        // turn the list into a properly-typed array for the elements
148        Object[] array = (Object[]) Array.newInstance(klass, decoded.size());
149        return decoded.toArray(array);
150    }
151
152    @Override
153    @SuppressWarnings("rawtypes")
154    public boolean validate(Object object) throws TypeException {
155        throw new UnsupportedOperationException("Unimplemented, use DocumentValidationService");
156    }
157
158    @Override
159    public Object newInstance() {
160        Object defaultValue = this.defaultValue;
161        if (defaultValue != null) {
162            return defaultValue;
163        } else {
164            // XXX AT: maybe use the type to be more specific on list elements
165            return new ArrayList<>();
166        }
167    }
168
169    @Override
170    @SuppressWarnings("unchecked")
171    public Object convert(Object object) throws TypeException {
172        if (object instanceof List) {
173            List<Object> list = (List<Object>) object;
174            for (int i = 0, len = list.size(); i < len; i++) {
175                Object value = list.get(i);
176                list.set(i, type.convert(value));
177            }
178            return object;
179        }
180        throw new TypeException("Incompatible object: " + object.getClass() + " for type " + getName());
181    }
182
183    @Override
184    public boolean isArray() {
185        return isArray;
186    }
187
188    @Override
189    public boolean isScalarList() {
190        return field.getType().isSimpleType();
191    }
192}