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(getPath()); 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(getPath()); 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 if (!children.remove(property)) { // physically remove the property 271 return false; // no such item 272 } 273 setIsModified(); 274 return true; 275 } 276 277 @Override 278 public Property remove(int index) { 279 Property property = children.remove(index); 280 setIsModified(); 281 return property; 282 } 283 284 @Override 285 public Object clone() throws CloneNotSupportedException { 286 ListProperty clone = (ListProperty) super.clone(); 287 return clone; 288 } 289 290 @Override 291 public void accept(PropertyVisitor visitor, Object arg) throws PropertyException { 292 arg = visitor.visit(this, arg); 293 if (arg != null && isContainer()) { 294 for (Property property : children) { 295 property.accept(visitor, arg); 296 } 297 } 298 } 299 300 /* ---------------------------- type conversion ------------------------ */ 301 302 @Override 303 public boolean isNormalized(Object value) { 304 return value == null || (value instanceof Collection && value instanceof Serializable); 305 } 306 307 @Override 308 public Serializable normalize(Object value) throws PropertyConversionException { 309 if (isNormalized(value)) { 310 return (Serializable) value; 311 } 312 if (value.getClass().isArray()) { 313 return arrayToList(value); 314 } 315 throw new PropertyConversionException(value.getClass(), List.class); 316 } 317 318 @SuppressWarnings("unchecked") 319 @Override 320 public <T> T convertTo(Serializable value, Class<T> toType) throws PropertyConversionException { 321 if (value == null) { 322 return null; 323 } else if (toType.isAssignableFrom(value.getClass())) { 324 return toType.cast(value); 325 } 326 if (toType.isArray()) { 327 return (T) ((Collection<?>) value).toArray(); 328 } else if (toType == List.class || toType == Collection.class) { 329 // TODO we need this for compatibility with scalar lists 330 if (value.getClass().isArray()) { 331 if (value.getClass().isPrimitive()) { 332 return (T) PrimitiveArrays.toList(value); 333 } else { 334 return (T) Arrays.asList((Object[]) value); 335 } 336 } 337 } 338 return super.convertTo(value, toType); 339 } 340 341 // Must return ArrayList 342 public static ArrayList<?> arrayToList(Object obj) { 343 Object[] ar = PrimitiveArrays.toObjectArray(obj); 344 ArrayList<Object> list = new ArrayList<Object>(ar.length); 345 list.addAll(Arrays.asList(ar)); 346 return list; 347 } 348 349 /** 350 * Supports ListDiff for compatibility. 351 * 352 * @param ld 353 */ 354 public void applyListDiff(ListDiff ld) throws PropertyException { 355 for (ListDiff.Entry entry : ld.diff()) { 356 switch (entry.type) { 357 case ListDiff.ADD: 358 addValue(entry.value); 359 break; 360 case ListDiff.INSERT: 361 addValue(entry.index, entry.value); 362 break; 363 case ListDiff.REMOVE: 364 remove(entry.index); 365 break; 366 case ListDiff.CLEAR: 367 clear(); 368 break; 369 case ListDiff.MODIFY: 370 get(entry.index).setValue(entry.value); 371 break; 372 case ListDiff.MOVE: 373 int toIndex = (Integer) entry.value; 374 int fromIndex = entry.index; 375 Property src = children.get(fromIndex); 376 src.moveTo(toIndex); 377 break; 378 } 379 } 380 } 381 382 @Override 383 public boolean isSameAs(Property property) throws PropertyException { 384 if (!(property instanceof ListProperty)) { 385 return false; 386 } 387 ListProperty lp = (ListProperty) property; 388 List<Property> c1 = children; 389 List<Property> c2 = lp.children; 390 if (c1.size() != c2.size()) { 391 return false; 392 } 393 for (int i = 0, size = c1.size(); i < size; i++) { 394 Property p1 = c1.get(i); 395 Property p2 = c2.get(i); 396 if (!p1.isSameAs(p2)) { 397 return false; 398 } 399 } 400 return true; 401 } 402 403 @Override 404 public Iterator<Property> getDirtyChildren() { 405 if (!isContainer()) { 406 throw new UnsupportedOperationException("Cannot iterate over children of scalar properties"); 407 } 408 return new DirtyPropertyIterator(children.iterator()); 409 } 410 411 public int indexOf(Property property) { 412 for (int i = 0, size = children.size(); i < size; i++) { 413 Property p = children.get(i); 414 if (p == property) { 415 return i; 416 } 417 } 418 return -1; 419 } 420 421 boolean moveTo(Property property, int index) { 422 if (index < 0 || index > children.size()) { 423 throw new IndexOutOfBoundsException("Index out of bounds: " + index + ". Bounds are: 0 - " 424 + children.size()); 425 } 426 int i = indexOf(property); 427 if (i == -1) { 428 throw new UnsupportedOperationException("You are trying to move a property that is not part of a list"); 429 } 430 if (i == index) { 431 return false; 432 } 433 if (i < index) { 434 children.add(index + 1, property); 435 children.remove(i); 436 } else { 437 children.add(index, property); 438 children.remove(i + 1); 439 } 440 // new property must be dirty 441 for (int j = Math.min(index, i); j < children.size(); j++) { 442 ((AbstractProperty) children.get(j)).setIsModified(); 443 } 444 return true; 445 } 446 447 /** 448 * Throws UnsupportedOperationException, added to implement List<Property> interface 449 */ 450 @Override 451 public void add(int index, Property element) { 452 throw new UnsupportedOperationException(); 453 } 454 455 /** 456 * Throws UnsupportedOperationException, added to implement List<Property> interface 457 */ 458 @Override 459 public boolean add(Property o) { 460 throw new UnsupportedOperationException(); 461 } 462 463 /** 464 * Throws UnsupportedOperationException, added to implement List<Property> interface 465 */ 466 @Override 467 public boolean addAll(Collection<? extends Property> c) { 468 throw new UnsupportedOperationException(); 469 } 470 471 /** 472 * Throws UnsupportedOperationException, added to implement List<Property> interface 473 */ 474 @Override 475 public boolean addAll(int index, Collection<? extends Property> c) { 476 throw new UnsupportedOperationException(); 477 } 478 479 /** 480 * Throws UnsupportedOperationException, added to implement List<Property> interface 481 */ 482 @Override 483 public boolean contains(Object o) { 484 throw new UnsupportedOperationException(); 485 } 486 487 /** 488 * Throws UnsupportedOperationException, added to implement List<Property> interface 489 */ 490 @Override 491 public boolean containsAll(Collection<?> c) { 492 throw new UnsupportedOperationException(); 493 } 494 495 /** 496 * Throws UnsupportedOperationException, added to implement List<Property> interface 497 */ 498 @Override 499 public int indexOf(Object o) { 500 throw new UnsupportedOperationException(); 501 } 502 503 @Override 504 public boolean isEmpty() { 505 return children.isEmpty(); 506 } 507 508 /** 509 * Throws UnsupportedOperationException, added to implement List<Property> interface 510 */ 511 @Override 512 public int lastIndexOf(Object o) { 513 throw new UnsupportedOperationException(); 514 } 515 516 /** 517 * Throws UnsupportedOperationException, added to implement List<Property> interface 518 */ 519 @Override 520 public ListIterator<Property> listIterator() { 521 return children.listIterator(); 522 } 523 524 /** 525 * Throws UnsupportedOperationException, added to implement List<Property> interface 526 */ 527 @Override 528 public ListIterator<Property> listIterator(int index) { 529 return children.listIterator(index); 530 } 531 532 /** 533 * Throws UnsupportedOperationException, added to implement List<Property> interface 534 */ 535 @Override 536 public boolean remove(Object o) { 537 throw new UnsupportedOperationException(); 538 } 539 540 /** 541 * Throws UnsupportedOperationException, added to implement List<Property> interface 542 */ 543 @Override 544 public boolean removeAll(Collection<?> c) { 545 throw new UnsupportedOperationException(); 546 } 547 548 /** 549 * Throws UnsupportedOperationException, added to implement List<Property> interface 550 */ 551 @Override 552 public boolean retainAll(Collection<?> c) { 553 throw new UnsupportedOperationException(); 554 } 555 556 /** 557 * Throws UnsupportedOperationException, added to implement List<Property> interface 558 */ 559 @Override 560 public Property set(int index, Property element) { 561 throw new UnsupportedOperationException(); 562 } 563 564 /** 565 * Throws UnsupportedOperationException, added to implement List<Property> interface 566 */ 567 @Override 568 public List<Property> subList(int fromIndex, int toIndex) { 569 throw new UnsupportedOperationException(); 570 } 571 572 @Override 573 public Object[] toArray() { 574 return children.toArray(); 575 } 576 577 @Override 578 public <T> T[] toArray(T[] a) { 579 return children.toArray(a); 580 } 581 582 @Override 583 public void clearDirtyFlags() { 584 // even makes child properties not dirty 585 super.clearDirtyFlags(); 586 for (Property child : children) { 587 child.clearDirtyFlags(); 588 } 589 } 590 591}