001/* 002 * Copyright (c) 2006-2014 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.api.model.impl; 014 015import java.io.Serializable; 016import java.util.ArrayList; 017import java.util.Arrays; 018import java.util.Collection; 019import java.util.Collections; 020import java.util.Iterator; 021import java.util.List; 022import java.util.ListIterator; 023 024import org.nuxeo.common.collections.PrimitiveArrays; 025import org.nuxeo.ecm.core.api.ListDiff; 026import org.nuxeo.ecm.core.api.PropertyException; 027import org.nuxeo.ecm.core.api.model.InvalidPropertyValueException; 028import org.nuxeo.ecm.core.api.model.Property; 029import org.nuxeo.ecm.core.api.model.PropertyConversionException; 030import org.nuxeo.ecm.core.api.model.PropertyVisitor; 031import org.nuxeo.ecm.core.api.model.ReadOnlyPropertyException; 032import org.nuxeo.ecm.core.schema.types.Field; 033import org.nuxeo.ecm.core.schema.types.ListType; 034 035public class ListProperty extends AbstractProperty implements List<Property> { 036 037 private static final long serialVersionUID = 1L; 038 039 /** 040 * The corresponding field. 041 */ 042 protected final Field field; 043 044 protected final List<Property> children; 045 046 public ListProperty(Property parent, Field field) { 047 super(parent); 048 this.field = field; 049 children = new ArrayList<Property>(); 050 } 051 052 public ListProperty(Property parent, Field field, int flags) { 053 super(parent, flags); 054 this.field = field; 055 children = new ArrayList<Property>(); 056 } 057 058 @Override 059 public void internalSetValue(Serializable value) throws PropertyException { 060 } 061 062 /** 063 * TODO FIXME XXX uncommented <code>return true;</code> see NXP-1653. 064 * 065 * @see DefaultPropertyFactory line 216 066 * @see {@link ListProperty#getValue} 067 * @see {@link ListProperty#accept} 068 */ 069 @Override 070 public boolean isContainer() { 071 // return true; // - this can be uncommented when scalar list will be 072 // fixed 073 return !getType().isScalarList(); 074 } 075 076 @Override 077 public Property addValue(int index, Object value) throws PropertyException { 078 Field lfield = getType().getField(); 079 Property property = getRoot().createProperty(this, lfield, IS_NEW); 080 property.setValue(value); 081 children.add(index, property); 082 return property; 083 } 084 085 @Override 086 public Property addValue(Object value) throws PropertyException { 087 Field lfield = getType().getField(); 088 Property property = getRoot().createProperty(this, lfield, IS_NEW); 089 property.setValue(value); 090 children.add(property); 091 return property; 092 } 093 094 @Override 095 public Property addEmpty() { 096 Field lfield = getType().getField(); 097 Property property = getRoot().createProperty(this, lfield, 0); 098 children.add(property); 099 return property; 100 } 101 102 @Override 103 public Collection<Property> getChildren() { 104 return Collections.unmodifiableCollection(children); 105 } 106 107 @Override 108 public String getName() { 109 return field.getName().getPrefixedName(); 110 } 111 112 @Override 113 public ListType getType() { 114 return (ListType) field.getType(); 115 } 116 117 @Override 118 public Property get(String name) { 119 try { 120 return get(Integer.parseInt(name)); 121 } catch (NumberFormatException e) { 122 return null; 123 } 124 } 125 126 @Override 127 public Property get(int index) { 128 try { 129 return children.get(index); 130 } catch (IndexOutOfBoundsException e) { 131 return null; 132 } 133 } 134 135 @Override 136 protected Serializable getDefaultValue() { 137 Serializable value = (Serializable) field.getDefaultValue(); 138 if (value == null) { 139 value = new ArrayList<Serializable>(); 140 } 141 return value; 142 } 143 144 @Override 145 public Serializable internalGetValue() throws PropertyException { 146 if (children.isEmpty()) { 147 return new ArrayList<String>(); 148 } 149 // noinspection CollectionDeclaredAsConcreteClass 150 ArrayList<Object> list = new ArrayList<Object>(children.size()); 151 for (Property property : children) { 152 list.add(property.getValue()); 153 } 154 // TODO XXX FIXME for compatibility - this treats sclar lists as array 155 // see NXP-1653. remove this when will be fixed 156 // see also isContainer(), setValue() and accept() 157 // if (getType().isScalarList()) { 158 // if (list.isEmpty()) return null; 159 // Object o = list.get(0); 160 // Class<?> type = o.getClass(); 161 // if (o == null) { 162 // // don't know the class of the element 163 // // try to use schema information 164 // type = JavaTypes.getPrimitiveClass(getType().getFieldType()); 165 // if (type == null) { // this should be a bug 166 // throw new IllegalStateException("Scalar list type is not known - this 167 // should be a bug"); 168 // } 169 // } else { 170 // type = o.getClass(); 171 // } 172 // return list.toArray((Object[])Array.newInstance(type, list.size())); 173 // } 174 // end of compatibility code <-------- 175 return list; 176 } 177 178 @Override 179 public Serializable getValueForWrite() throws PropertyException { 180 if (isPhantom() || isRemoved()) { 181 return getDefaultValue(); 182 } 183 if (children.isEmpty()) { 184 return new ArrayList<String>(); 185 } 186 // noinspection CollectionDeclaredAsConcreteClass 187 ArrayList<Object> list = new ArrayList<Object>(children.size()); 188 for (Property property : children) { 189 list.add(property.getValueForWrite()); 190 } 191 return list; 192 } 193 194 @Override 195 @SuppressWarnings("unchecked") 196 public void init(Serializable value) throws PropertyException { 197 if (value == null) { // IGNORE null values - properties will be 198 // considered PHANTOMS 199 return; 200 } 201 List<Serializable> list; 202 if (value.getClass().isArray()) { // accept also arrays 203 list = (List<Serializable>) PrimitiveArrays.toList(value); 204 } else { 205 list = (List<Serializable>) value; 206 } 207 children.clear(); // do not use clear() method since it is marking the 208 // list as dirty 209 Field lfield = getType().getField(); 210 for (Serializable obj : list) { 211 Property property = getRoot().createProperty(this, lfield, 0); 212 property.init(obj); 213 children.add(property); 214 } 215 removePhantomFlag(); 216 } 217 218 @Override 219 public void setValue(Object value) throws PropertyException { 220 if (isReadOnly()) { 221 throw new ReadOnlyPropertyException(getPath()); 222 } 223 if (value == null) { 224 List<Property> temp = new ArrayList<Property>(children); 225 for (Property p : temp) { // remove all children 226 p.remove(); 227 } 228 return; 229 } 230 Collection<?> col; 231 Class<?> klass = value.getClass(); 232 if (klass == ListDiff.class) { // listdiff support for compatibility 233 applyListDiff((ListDiff) value); 234 return; 235 } else if (klass.isArray()) { // array support 236 col = arrayToList(value); 237 } else if (value instanceof Collection) { // collection support 238 col = (Collection<?>) value; 239 } else { 240 throw new InvalidPropertyValueException(getPath()); 241 } 242 clear(); 243 Field lfield = getType().getField(); 244 for (Object obj : col) { 245 Property property = getRoot().createProperty(this, lfield, IS_NEW); 246 property.setValue(obj); 247 children.add(property); 248 } 249 } 250 251 @Override 252 public void clear() { 253 children.clear(); 254 setIsModified(); 255 } 256 257 @Override 258 public Field getField() { 259 return field; 260 } 261 262 public boolean remove(Property property) { 263 if (!children.remove(property)) { // physically remove the property 264 return false; // no such item 265 } 266 setIsModified(); 267 return true; 268 } 269 270 @Override 271 public Property remove(int index) { 272 Property property = children.remove(index); 273 setIsModified(); 274 return property; 275 } 276 277 @Override 278 public Object clone() throws CloneNotSupportedException { 279 ListProperty clone = (ListProperty) super.clone(); 280 return clone; 281 } 282 283 @Override 284 public void accept(PropertyVisitor visitor, Object arg) throws PropertyException { 285 arg = visitor.visit(this, arg); 286 if (arg != null && isContainer()) { 287 for (Property property : children) { 288 property.accept(visitor, arg); 289 } 290 } 291 } 292 293 /* ---------------------------- type conversion ------------------------ */ 294 295 @Override 296 public boolean isNormalized(Object value) { 297 return value == null || (value instanceof Collection && value instanceof Serializable); 298 } 299 300 @Override 301 public Serializable normalize(Object value) throws PropertyConversionException { 302 if (isNormalized(value)) { 303 return (Serializable) value; 304 } 305 if (value.getClass().isArray()) { 306 return arrayToList(value); 307 } 308 throw new PropertyConversionException(value.getClass(), List.class); 309 } 310 311 @SuppressWarnings("unchecked") 312 @Override 313 public <T> T convertTo(Serializable value, Class<T> toType) throws PropertyConversionException { 314 if (value == null) { 315 return null; 316 } else if (toType.isAssignableFrom(value.getClass())) { 317 return toType.cast(value); 318 } 319 if (toType.isArray()) { 320 return (T) ((Collection<?>) value).toArray(); 321 } else if (toType == List.class || toType == Collection.class) { 322 // TODO we need this for compatibility with scalar lists 323 if (value.getClass().isArray()) { 324 if (value.getClass().isPrimitive()) { 325 return (T) PrimitiveArrays.toList(value); 326 } else { 327 return (T) Arrays.asList((Object[]) value); 328 } 329 } 330 } 331 return super.convertTo(value, toType); 332 } 333 334 // Must return ArrayList 335 public static ArrayList<?> arrayToList(Object obj) { 336 Object[] ar = PrimitiveArrays.toObjectArray(obj); 337 ArrayList<Object> list = new ArrayList<Object>(ar.length); 338 list.addAll(Arrays.asList(ar)); 339 return list; 340 } 341 342 /** 343 * Supports ListDiff for compatibility. 344 * 345 * @param ld 346 */ 347 public void applyListDiff(ListDiff ld) throws PropertyException { 348 for (ListDiff.Entry entry : ld.diff()) { 349 switch (entry.type) { 350 case ListDiff.ADD: 351 addValue(entry.value); 352 break; 353 case ListDiff.INSERT: 354 addValue(entry.index, entry.value); 355 break; 356 case ListDiff.REMOVE: 357 remove(entry.index); 358 break; 359 case ListDiff.CLEAR: 360 clear(); 361 break; 362 case ListDiff.MODIFY: 363 get(entry.index).setValue(entry.value); 364 break; 365 case ListDiff.MOVE: 366 int toIndex = (Integer) entry.value; 367 int fromIndex = entry.index; 368 Property src = children.get(fromIndex); 369 src.moveTo(toIndex); 370 break; 371 } 372 } 373 } 374 375 @Override 376 public boolean isSameAs(Property property) throws PropertyException { 377 if (!(property instanceof ListProperty)) { 378 return false; 379 } 380 ListProperty lp = (ListProperty) property; 381 List<Property> c1 = children; 382 List<Property> c2 = lp.children; 383 if (c1.size() != c2.size()) { 384 return false; 385 } 386 for (int i = 0, size = c1.size(); i < size; i++) { 387 Property p1 = c1.get(i); 388 Property p2 = c2.get(i); 389 if (!p1.isSameAs(p2)) { 390 return false; 391 } 392 } 393 return true; 394 } 395 396 @Override 397 public Iterator<Property> getDirtyChildren() { 398 if (!isContainer()) { 399 throw new UnsupportedOperationException("Cannot iterate over children of scalar properties"); 400 } 401 return new DirtyPropertyIterator(children.iterator()); 402 } 403 404 public int indexOf(Property property) { 405 for (int i = 0, size = children.size(); i < size; i++) { 406 Property p = children.get(i); 407 if (p == property) { 408 return i; 409 } 410 } 411 return -1; 412 } 413 414 boolean moveTo(Property property, int index) { 415 if (index < 0 || index > children.size()) { 416 throw new IndexOutOfBoundsException("Index out of bounds: " + index + ". Bounds are: 0 - " 417 + children.size()); 418 } 419 int i = indexOf(property); 420 if (i == -1) { 421 throw new UnsupportedOperationException("You are trying to move a property that is not part of a list"); 422 } 423 if (i == index) { 424 return false; 425 } 426 if (i < index) { 427 children.add(index + 1, property); 428 children.remove(i); 429 } else { 430 children.add(index, property); 431 children.remove(i + 1); 432 } 433 // new property must be dirty 434 for (int j = Math.min(index, i); j < children.size(); j++) { 435 ((AbstractProperty) children.get(j)).setIsModified(); 436 } 437 return true; 438 } 439 440 /** 441 * Throws UnsupportedOperationException, added to implement List<Property> interface 442 */ 443 @Override 444 public void add(int index, Property element) { 445 throw new UnsupportedOperationException(); 446 } 447 448 /** 449 * Throws UnsupportedOperationException, added to implement List<Property> interface 450 */ 451 @Override 452 public boolean add(Property o) { 453 throw new UnsupportedOperationException(); 454 } 455 456 /** 457 * Throws UnsupportedOperationException, added to implement List<Property> interface 458 */ 459 @Override 460 public boolean addAll(Collection<? extends Property> c) { 461 throw new UnsupportedOperationException(); 462 } 463 464 /** 465 * Throws UnsupportedOperationException, added to implement List<Property> interface 466 */ 467 @Override 468 public boolean addAll(int index, Collection<? extends Property> c) { 469 throw new UnsupportedOperationException(); 470 } 471 472 /** 473 * Throws UnsupportedOperationException, added to implement List<Property> interface 474 */ 475 @Override 476 public boolean contains(Object o) { 477 throw new UnsupportedOperationException(); 478 } 479 480 /** 481 * Throws UnsupportedOperationException, added to implement List<Property> interface 482 */ 483 @Override 484 public boolean containsAll(Collection<?> c) { 485 throw new UnsupportedOperationException(); 486 } 487 488 /** 489 * Throws UnsupportedOperationException, added to implement List<Property> interface 490 */ 491 @Override 492 public int indexOf(Object o) { 493 throw new UnsupportedOperationException(); 494 } 495 496 @Override 497 public boolean isEmpty() { 498 return children.isEmpty(); 499 } 500 501 /** 502 * Throws UnsupportedOperationException, added to implement List<Property> interface 503 */ 504 @Override 505 public int lastIndexOf(Object o) { 506 throw new UnsupportedOperationException(); 507 } 508 509 /** 510 * Throws UnsupportedOperationException, added to implement List<Property> interface 511 */ 512 @Override 513 public ListIterator<Property> listIterator() { 514 return children.listIterator(); 515 } 516 517 /** 518 * Throws UnsupportedOperationException, added to implement List<Property> interface 519 */ 520 @Override 521 public ListIterator<Property> listIterator(int index) { 522 return children.listIterator(index); 523 } 524 525 /** 526 * Throws UnsupportedOperationException, added to implement List<Property> interface 527 */ 528 @Override 529 public boolean remove(Object o) { 530 throw new UnsupportedOperationException(); 531 } 532 533 /** 534 * Throws UnsupportedOperationException, added to implement List<Property> interface 535 */ 536 @Override 537 public boolean removeAll(Collection<?> c) { 538 throw new UnsupportedOperationException(); 539 } 540 541 /** 542 * Throws UnsupportedOperationException, added to implement List<Property> interface 543 */ 544 @Override 545 public boolean retainAll(Collection<?> c) { 546 throw new UnsupportedOperationException(); 547 } 548 549 /** 550 * Throws UnsupportedOperationException, added to implement List<Property> interface 551 */ 552 @Override 553 public Property set(int index, Property element) { 554 throw new UnsupportedOperationException(); 555 } 556 557 /** 558 * Throws UnsupportedOperationException, added to implement List<Property> interface 559 */ 560 @Override 561 public List<Property> subList(int fromIndex, int toIndex) { 562 throw new UnsupportedOperationException(); 563 } 564 565 @Override 566 public Object[] toArray() { 567 return children.toArray(); 568 } 569 570 @Override 571 public <T> T[] toArray(T[] a) { 572 return children.toArray(a); 573 } 574 575 @Override 576 public void clearDirtyFlags() { 577 // even makes child properties not dirty 578 super.clearDirtyFlags(); 579 for (Property child : children) { 580 child.clearDirtyFlags(); 581 } 582 } 583 584}