001/*
002 * (C) Copyright 2006-2012 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.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.lang.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<Constraint>(), 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        if (object == null) {
156            return true;
157        }
158        if (object instanceof Collection) {
159            return validateCollection((Collection) object);
160        } else if (object.getClass().isArray()) {
161            return validateArray((Object[]) object);
162        }
163        return false;
164    }
165
166    protected boolean validateArray(Object[] array) {
167        return true; // TODO
168    }
169
170    @SuppressWarnings("rawtypes")
171    protected boolean validateCollection(Collection col) {
172        return true; // TODO
173    }
174
175    @Override
176    public Object newInstance() {
177        Object defaultValue = this.defaultValue;
178        if (defaultValue != null) {
179            return defaultValue;
180        } else {
181            // XXX AT: maybe use the type to be more specific on list elements
182            return new ArrayList<Object>();
183        }
184    }
185
186    @Override
187    @SuppressWarnings("unchecked")
188    public Object convert(Object object) throws TypeException {
189        if (object instanceof List) {
190            List<Object> list = (List<Object>) object;
191            for (int i = 0, len = list.size(); i < len; i++) {
192                Object value = list.get(i);
193                list.set(i, type.convert(value));
194            }
195            return object;
196        }
197        throw new TypeException("Incompatible object: " + object.getClass() + " for type " + getName());
198    }
199
200    @Override
201    public boolean isArray() {
202        return isArray;
203    }
204
205    @Override
206    public boolean isScalarList() {
207        return field.getType().isSimpleType();
208    }
209}