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