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