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.util.ArrayList;
023import java.util.Collection;
024import java.util.HashMap;
025import java.util.Map;
026import java.util.Map.Entry;
027
028import org.nuxeo.ecm.core.schema.Namespace;
029import org.nuxeo.ecm.core.schema.TypeConstants;
030import org.nuxeo.ecm.core.schema.types.constraints.Constraint;
031
032/**
033 * A Complex Type holds several fields.
034 */
035public class ComplexTypeImpl extends AbstractType implements ComplexType {
036
037    private static final long serialVersionUID = 1L;
038
039    /** The fields held by this complex type. */
040    protected final Map<QName, Field> fields = new HashMap<QName, Field>();
041
042    /** The map of name or prefixed name to field. */
043    protected volatile Map<String, Field> fieldsByName = new HashMap<String, Field>();
044
045    protected final Namespace ns;
046
047    public ComplexTypeImpl(ComplexType superType, String schema, String name, Namespace ns) {
048        super(superType, schema, name);
049        // for composite types, they already include schemas from supertypes
050        // if (superType != null) {
051        // for (Field field : superType.getFields()) {
052        // addField(field);
053        // }
054        // }
055        this.ns = ns;
056    }
057
058    public ComplexTypeImpl(ComplexType superType, String schema, String name) {
059        this(superType, schema, name, Namespace.DEFAULT_NS);
060    }
061
062    // also called by CompositeTypeImpl
063    protected void addField(Field field) {
064        QName name = field.getName();
065        fields.put(name, field);
066        fieldsByName.put(name.getLocalName(), field);
067        fieldsByName.put(name.getPrefixedName(), field);
068    }
069
070    // called by XSDLoader
071    @Override
072    public Field addField(String name, Type type, String defaultValue, int flags, Collection<Constraint> constraints) {
073        QName qname = QName.valueOf(name, ns.prefix);
074        FieldImpl field = new FieldImpl(qname, this, type, defaultValue, flags, constraints);
075        addField(field);
076        return field;
077    }
078
079    @Override
080    public Namespace getNamespace() {
081        return ns;
082    }
083
084    @Override
085    public Field getField(String name) {
086        return fieldsByName.get(name);
087    }
088
089    @Override
090    public Field getField(QName name) {
091        return fields.get(name);
092    }
093
094    @Override
095    public Collection<Field> getFields() {
096        return fields.values();
097    }
098
099    @Override
100    public int getFieldsCount() {
101        return fields.size();
102    }
103
104    @Override
105    public boolean hasField(String name) {
106        return fieldsByName.containsKey(name);
107    }
108
109    @Override
110    public boolean hasFields() {
111        return !fields.isEmpty();
112    }
113
114    @Override
115    public boolean isComplexType() {
116        return true;
117    }
118
119    @SuppressWarnings("unchecked")
120    @Override
121    public boolean validate(Object object) throws TypeException {
122        throw new UnsupportedOperationException("Unimplemented, use DocumentValidationService");
123    }
124
125    @Override
126    public String toString() {
127        return getClass().getSimpleName() + '(' + name + ')';
128    }
129
130    @Override
131    public Map<String, Object> newInstance() {
132        if (TypeConstants.isContentType(this)) {
133            // NXP-912: should return null for a blob. Since there is no
134            // pluggable adapter mechanism on types, and since document model
135            // properties consider that every complex property named "content"
136            // should be dealt with a BlobProperty, this is hardcoded here.
137            return null;
138        }
139        Map<String, Object> map = new HashMap<String, Object>();
140        for (Field field : fields.values()) {
141            Object value;
142            Type type = field.getType();
143            if (type.isComplexType()) {
144                value = type.newInstance();
145            } else if (type.isListType()) {
146                value = new ArrayList<Object>();
147            } else {
148                value = field.getDefaultValue();
149            }
150            map.put(field.getName().getLocalName(), value);
151        }
152        return map;
153    }
154
155    @Override
156    @SuppressWarnings("unchecked")
157    public Object convert(Object object) throws TypeException {
158        if (object instanceof Map) {
159            Map<Object, Object> map = (Map<Object, Object>) object;
160            for (Entry<Object, Object> entry : map.entrySet()) {
161                String key = entry.getKey().toString();
162                Field field = getField(key);
163                if (field == null) {
164                    throw new IllegalArgumentException("Field " + key + " is not defined for the complex type "
165                            + getName());
166                }
167                entry.setValue(field.getType().convert(entry.getValue()));
168            }
169            return object;
170        }
171        throw new TypeException("Incompatible object: " + object.getClass() + " for type " + this);
172    }
173
174    /**
175     * Canonicalizes a Nuxeo-xpath.
176     * <p>
177     * Replaces {@code a/foo[123]/b} with {@code a/123/b}
178     * <p>
179     * A star can be used instead of the digits as well (for configuration).
180     *
181     * @param xpath the xpath
182     * @return the canonicalized xpath.
183     */
184    public static String canonicalXPath(String xpath) {
185        while (xpath.length() > 0 && xpath.charAt(0) == '/') {
186            xpath = xpath.substring(1);
187        }
188        if (xpath.indexOf('[') == -1) {
189            return xpath;
190        } else {
191            return xpath.replaceAll("[^/\\[\\]]+\\[(\\d+|\\*)\\]", "$1");
192        }
193    }
194
195}