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        if (object == null) {
123            return true;
124        }
125        if (object instanceof Map) {
126            return validateMap((Map<Object, Object>) object);
127        }
128        return false;
129    }
130
131    protected boolean validateMap(Map<Object, Object> map) {
132        return true;
133    }
134
135    @Override
136    public String toString() {
137        return getClass().getSimpleName() + '(' + name + ')';
138    }
139
140    @Override
141    public Map<String, Object> newInstance() {
142        if (TypeConstants.isContentType(this)) {
143            // NXP-912: should return null for a blob. Since there is no
144            // pluggable adapter mechanism on types, and since document model
145            // properties consider that every complex property named "content"
146            // should be dealt with a BlobProperty, this is hardcoded here.
147            return null;
148        }
149        Map<String, Object> map = new HashMap<String, Object>();
150        for (Field field : fields.values()) {
151            Object value;
152            Type type = field.getType();
153            if (type.isComplexType()) {
154                value = type.newInstance();
155            } else if (type.isListType()) {
156                value = new ArrayList<Object>();
157            } else {
158                value = field.getDefaultValue();
159            }
160            map.put(field.getName().getLocalName(), value);
161        }
162        return map;
163    }
164
165    @Override
166    @SuppressWarnings("unchecked")
167    public Object convert(Object object) throws TypeException {
168        if (object instanceof Map) {
169            Map<Object, Object> map = (Map<Object, Object>) object;
170            for (Entry<Object, Object> entry : map.entrySet()) {
171                String key = entry.getKey().toString();
172                Field field = getField(key);
173                if (field == null) {
174                    throw new IllegalArgumentException("Field " + key + " is not defined for the complex type "
175                            + getName());
176                }
177                entry.setValue(field.getType().convert(entry.getValue()));
178            }
179            return object;
180        }
181        throw new TypeException("Incompatible object: " + object.getClass() + " for type " + this);
182    }
183
184    /**
185     * Canonicalizes a Nuxeo-xpath.
186     * <p>
187     * Replaces {@code a/foo[123]/b} with {@code a/123/b}
188     * <p>
189     * A star can be used instead of the digits as well (for configuration).
190     *
191     * @param xpath the xpath
192     * @return the canonicalized xpath.
193     */
194    public static String canonicalXPath(String xpath) {
195        while (xpath.length() > 0 && xpath.charAt(0) == '/') {
196            xpath = xpath.substring(1);
197        }
198        if (xpath.indexOf('[') == -1) {
199            return xpath;
200        } else {
201            return xpath.replaceAll("[^/\\[\\]]+\\[(\\d+|\\*)\\]", "$1");
202        }
203    }
204
205}