/*
* (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Bogdan Stefanescu
* Florent Guillaume
*/
package org.nuxeo.ecm.core.schema.types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.nuxeo.ecm.core.schema.Namespace;
import org.nuxeo.ecm.core.schema.TypeConstants;
import org.nuxeo.ecm.core.schema.types.constraints.Constraint;
/**
* A Complex Type holds several fields.
*/
public class ComplexTypeImpl extends AbstractType implements ComplexType {
private static final long serialVersionUID = 1L;
/** The fields held by this complex type. */
protected final Map<QName, Field> fields = new HashMap<QName, Field>();
/** The map of name or prefixed name to field. */
protected volatile Map<String, Field> fieldsByName = new HashMap<String, Field>();
protected final Namespace ns;
public ComplexTypeImpl(ComplexType superType, String schema, String name, Namespace ns) {
super(superType, schema, name);
// for composite types, they already include schemas from supertypes
// if (superType != null) {
// for (Field field : superType.getFields()) {
// addField(field);
// }
// }
this.ns = ns;
}
public ComplexTypeImpl(ComplexType superType, String schema, String name) {
this(superType, schema, name, Namespace.DEFAULT_NS);
}
// also called by CompositeTypeImpl
protected void addField(Field field) {
QName name = field.getName();
fields.put(name, field);
fieldsByName.put(name.getLocalName(), field);
fieldsByName.put(name.getPrefixedName(), field);
}
// called by XSDLoader
@Override
public Field addField(String name, Type type, String defaultValue, int flags, Collection<Constraint> constraints) {
QName qname = QName.valueOf(name, ns.prefix);
FieldImpl field = new FieldImpl(qname, this, type, defaultValue, flags, constraints);
addField(field);
return field;
}
@Override
public Namespace getNamespace() {
return ns;
}
@Override
public Field getField(String name) {
return fieldsByName.get(name);
}
@Override
public Field getField(QName name) {
return fields.get(name);
}
@Override
public Collection<Field> getFields() {
return fields.values();
}
@Override
public int getFieldsCount() {
return fields.size();
}
@Override
public boolean hasField(String name) {
return fieldsByName.containsKey(name);
}
@Override
public boolean hasFields() {
return !fields.isEmpty();
}
@Override
public boolean isComplexType() {
return true;
}
@SuppressWarnings("unchecked")
@Override
public boolean validate(Object object) throws TypeException {
if (object == null) {
return true;
}
if (object instanceof Map) {
return validateMap((Map<Object, Object>) object);
}
return false;
}
protected boolean validateMap(Map<Object, Object> map) {
return true;
}
@Override
public String toString() {
return getClass().getSimpleName() + '(' + name + ')';
}
@Override
public Map<String, Object> newInstance() {
if (TypeConstants.isContentType(this)) {
// NXP-912: should return null for a blob. Since there is no
// pluggable adapter mechanism on types, and since document model
// properties consider that every complex property named "content"
// should be dealt with a BlobProperty, this is hardcoded here.
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
for (Field field : fields.values()) {
Object value;
Type type = field.getType();
if (type.isComplexType()) {
value = type.newInstance();
} else if (type.isListType()) {
value = new ArrayList<Object>();
} else {
value = field.getDefaultValue();
}
map.put(field.getName().getLocalName(), value);
}
return map;
}
@Override
@SuppressWarnings("unchecked")
public Object convert(Object object) throws TypeException {
if (object instanceof Map) {
Map<Object, Object> map = (Map<Object, Object>) object;
for (Entry<Object, Object> entry : map.entrySet()) {
String key = entry.getKey().toString();
Field field = getField(key);
if (field == null) {
throw new IllegalArgumentException("Field " + key + " is not defined for the complex type "
+ getName());
}
entry.setValue(field.getType().convert(entry.getValue()));
}
return object;
}
throw new TypeException("Incompatible object: " + object.getClass() + " for type " + this);
}
/**
* Canonicalizes a Nuxeo-xpath.
* <p>
* Replaces {@code a/foo[123]/b} with {@code a/123/b}
* <p>
* A star can be used instead of the digits as well (for configuration).
*
* @param xpath the xpath
* @return the canonicalized xpath.
*/
public static String canonicalXPath(String xpath) {
while (xpath.length() > 0 && xpath.charAt(0) == '/') {
xpath = xpath.substring(1);
}
if (xpath.indexOf('[') == -1) {
return xpath;
} else {
return xpath.replaceAll("[^/\\[\\]]+\\[(\\d+|\\*)\\]", "$1");
}
}
}