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