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.Iterator; 017 018import org.nuxeo.common.utils.Path; 019import org.nuxeo.ecm.core.api.PropertyException; 020import org.nuxeo.ecm.core.api.model.DocumentPart; 021import org.nuxeo.ecm.core.api.model.Property; 022import org.nuxeo.ecm.core.api.model.PropertyConversionException; 023import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 024import org.nuxeo.ecm.core.api.model.ReadOnlyPropertyException; 025import org.nuxeo.ecm.core.api.model.resolver.PropertyObjectResolver; 026import org.nuxeo.ecm.core.api.model.resolver.PropertyObjectResolverImpl; 027import org.nuxeo.ecm.core.schema.types.Schema; 028import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver; 029 030public abstract class AbstractProperty implements Property { 031 032 private static final long serialVersionUID = 1L; 033 034 /** 035 * Whether or not this property is read only. 036 */ 037 public static final int IS_READONLY = 32; 038 039 public final Property parent; 040 041 /** 042 * for SimpleDocumentModel uses 043 */ 044 public boolean forceDirty = false; 045 046 protected int flags; 047 048 protected AbstractProperty(Property parent) { 049 this.parent = parent; 050 } 051 052 protected AbstractProperty(Property parent, int flags) { 053 this.parent = parent; 054 this.flags = flags; 055 } 056 057 /** 058 * Sets the given normalized value. 059 * <p> 060 * This applies only for nodes that physically store a value (that means non container nodes). Container nodes does 061 * nothing. 062 * 063 * @param value 064 */ 065 public abstract void internalSetValue(Serializable value) throws PropertyException; 066 067 public abstract Serializable internalGetValue() throws PropertyException; 068 069 @Override 070 public void init(Serializable value) throws PropertyException { 071 if (value == null) { // IGNORE null values - properties will be 072 // considered PHANTOMS 073 return; 074 } 075 internalSetValue(value); 076 removePhantomFlag(); 077 } 078 079 public void removePhantomFlag() { 080 flags &= ~IS_PHANTOM; 081 if (parent != null) { 082 ((AbstractProperty) parent).removePhantomFlag(); 083 } 084 } 085 086 @Override 087 public void setValue(int index, Object value) throws PropertyException { 088 Property property = get(index); 089 property.setValue(value); 090 } 091 092 @Override 093 public int size() { 094 return getChildren().size(); 095 } 096 097 @Override 098 public Iterator<Property> iterator() { 099 return getChildren().iterator(); 100 } 101 102 @Override 103 public Serializable remove() throws PropertyException { 104 Serializable value = getValue(); 105 if (parent != null && parent.isList()) { // remove from list is 106 // handled separately 107 ListProperty list = (ListProperty) parent; 108 list.remove(this); 109 } else if (!isPhantom()) { // remove from map is easier -> mark the 110 // field as removed and remove the value 111 // do not remove the field if the previous value was null, except if its a property from a SimpleDocumentModel (forceDirty mode) 112 Serializable previous = internalGetValue(); 113 init(null); 114 if (previous != null || isForceDirty()) { 115 setIsRemoved(); 116 } 117 } 118 return value; 119 } 120 121 @Override 122 public Property getParent() { 123 return parent; 124 } 125 126 @Override 127 public String getPath() { 128 Path path = collectPath(new Path("/")); 129 return path.toString(); 130 } 131 132 protected Path collectPath(Path path) { 133 String name = getName(); 134 if (parent != null) { 135 if (parent.isList()) { 136 int i = ((ListProperty) parent).children.indexOf(this); 137 name = name + '[' + i + ']'; 138 } 139 path = ((AbstractProperty) parent).collectPath(path); 140 } 141 return path.append(name); 142 } 143 144 @Override 145 public Schema getSchema() { 146 return getRoot().getSchema(); 147 } 148 149 @Override 150 public boolean isList() { 151 return getType().isListType(); 152 } 153 154 @Override 155 public boolean isComplex() { 156 return getType().isComplexType(); 157 } 158 159 @Override 160 public boolean isScalar() { 161 return getType().isSimpleType(); 162 } 163 164 @Override 165 public boolean isNew() { 166 return areFlagsSet(IS_NEW); 167 } 168 169 @Override 170 public boolean isRemoved() { 171 return areFlagsSet(IS_REMOVED); 172 } 173 174 @Override 175 public boolean isMoved() { 176 return areFlagsSet(IS_MOVED); 177 } 178 179 @Override 180 public boolean isModified() { 181 return areFlagsSet(IS_MODIFIED); 182 } 183 184 @Override 185 public boolean isPhantom() { 186 return areFlagsSet(IS_PHANTOM); 187 } 188 189 @Override 190 public final boolean isDirty() { 191 return (flags & IS_DIRTY) != 0; 192 } 193 194 protected final void setDirtyFlags(int dirtyFlags) { 195 flags = dirtyFlags & DIRTY_MASK | flags & ~DIRTY_MASK; 196 } 197 198 protected final void appendDirtyFlags(int dirtyFlags) { 199 flags |= (dirtyFlags & DIRTY_MASK); 200 } 201 202 @Override 203 public boolean isReadOnly() { 204 return areFlagsSet(IS_READONLY); 205 } 206 207 @Override 208 public void setReadOnly(boolean value) { 209 if (value) { 210 setFlags(IS_READONLY); 211 } else { 212 clearFlags(IS_READONLY); 213 } 214 } 215 216 public final boolean areFlagsSet(long flags) { 217 return (this.flags & flags) != 0; 218 } 219 220 public final void setFlags(long flags) { 221 this.flags |= flags; 222 } 223 224 public final void clearFlags(long flags) { 225 this.flags &= ~flags; 226 } 227 228 @Override 229 public int getDirtyFlags() { 230 return flags & DIRTY_MASK; 231 } 232 233 @Override 234 public void clearDirtyFlags() { 235 if ((flags & IS_REMOVED) != 0) { 236 // if is removed the property becomes a phantom 237 setDirtyFlags(IS_PHANTOM); 238 } else { 239 setDirtyFlags(NONE); 240 } 241 } 242 243 /** 244 * This method is public because of DataModelImpl which use it. 245 * <p> 246 * TODO after removing DataModelImpl make it protected. 247 */ 248 public void setIsModified() { 249 if ((flags & IS_MODIFIED) == 0) { // if not already modified 250 // clear dirty + phatom flag if any 251 flags |= IS_MODIFIED; // set the modified flag 252 flags &= ~IS_PHANTOM; // remove phantom flag if any 253 } 254 if (parent != null) { 255 ((AbstractProperty) parent).setIsModified(); 256 } 257 } 258 259 protected void setIsNew() { 260 if (isDirty()) { 261 throw new IllegalStateException("Cannot set IS_NEW flag on a dirty property"); 262 } 263 // clear dirty + phantom flag if any 264 setDirtyFlags(IS_NEW); // this clear any dirty flag and set the new 265 // flag 266 if (parent != null) { 267 ((AbstractProperty) parent).setIsModified(); 268 } 269 } 270 271 protected void setIsRemoved() { 272 if (isPhantom() || parent == null || parent.isList()) { 273 throw new IllegalStateException("Cannot set IS_REMOVED on removed or properties that are not map elements"); 274 } 275 if ((flags & IS_REMOVED) == 0) { // if not already removed 276 // clear dirty + phatom flag if any 277 setDirtyFlags(IS_REMOVED); 278 ((AbstractProperty) parent).setIsModified(); 279 } 280 } 281 282 protected void setIsMoved() { 283 if (parent == null || !parent.isList()) { 284 throw new IllegalStateException("Cannot set IS_MOVED on removed or properties that are not map elements"); 285 } 286 if ((flags & IS_MOVED) == 0) { 287 flags |= IS_MOVED; 288 ((AbstractProperty) parent).setIsModified(); 289 } 290 } 291 292 @Override 293 public <T> T getValue(Class<T> type) throws PropertyException { 294 return convertTo(getValue(), type); 295 } 296 297 @Override 298 public void setValue(Object value) throws PropertyException { 299 // 1. check the read only flag 300 if (isReadOnly()) { 301 throw new ReadOnlyPropertyException(getPath()); 302 } 303 // 1. normalize the value 304 Serializable normalizedValue = normalize(value); 305 // 2. backup the current 306 Serializable current = internalGetValue(); 307 // if its a phantom, no need to check for changes, set it dirty 308 if (!isSameValue(normalizedValue, current) || isForceDirty()) { 309 // 3. set the normalized value and 310 internalSetValue(normalizedValue); 311 // 4. update flags 312 setIsModified(); 313 } else { 314 removePhantomFlag(); 315 } 316 } 317 318 protected boolean isSameValue(Serializable value1, Serializable value2) { 319 return ((value1 == null && value2 == null) || (value1 != null && value1.equals(value2))); 320 } 321 322 @Override 323 public void setValue(String path, Object value) throws PropertyException { 324 resolvePath(path).setValue(value); 325 } 326 327 @Override 328 public <T> T getValue(Class<T> type, String path) throws PropertyException { 329 return resolvePath(path).getValue(type); 330 } 331 332 @Override 333 public Serializable getValue(String path) throws PropertyException { 334 return resolvePath(path).getValue(); 335 } 336 337 @Override 338 public Serializable getValue() throws PropertyException { 339 if (isPhantom() || isRemoved()) { 340 return getDefaultValue(); 341 } 342 return internalGetValue(); 343 } 344 345 @Override 346 public Serializable getValueForWrite() throws PropertyException { 347 return getValue(); 348 } 349 350 protected Serializable getDefaultValue() { 351 return (Serializable) getField().getDefaultValue(); 352 } 353 354 @Override 355 public void moveTo(int index) { 356 if (parent == null || !parent.isList()) { 357 throw new UnsupportedOperationException("Not a list item property"); 358 } 359 ListProperty list = (ListProperty) parent; 360 if (list.moveTo(this, index)) { 361 setIsMoved(); 362 } 363 } 364 365 @Override 366 public DocumentPart getRoot() { 367 return parent == null ? (DocumentPart) this : parent.getRoot(); 368 } 369 370 @Override 371 public Property resolvePath(String path) throws PropertyNotFoundException { 372 return resolvePath(new Path(path)); 373 } 374 375 @Override 376 public Property resolvePath(Path path) throws PropertyNotFoundException { 377 // handle absolute paths -> resolve them relative to the root 378 if (path.isAbsolute()) { 379 return getRoot().resolvePath(path.makeRelative()); 380 } 381 382 String[] segments = path.segments(); 383 // handle ../../ paths 384 Property property = this; 385 int start = 0; 386 for (; start < segments.length; start++) { 387 if (segments[start].equals("..")) { 388 property = property.getParent(); 389 } else { 390 break; 391 } 392 } 393 394 // now start resolving the path from 'start' depth relative to 395 // 'property' 396 for (int i = start; i < segments.length; i++) { 397 String segment = segments[i]; 398 if (property.isScalar()) { 399 throw new PropertyNotFoundException(path.toString(), "segment " + segment 400 + " points to a scalar property"); 401 } 402 String index = null; 403 if (segment.endsWith("]")) { 404 int p = segment.lastIndexOf('['); 405 if (p == -1) { 406 throw new PropertyNotFoundException(path.toString(), "Parse error: no matching '[' was found"); 407 } 408 index = segment.substring(p + 1, segment.length() - 1); 409 segment = segment.substring(0, p); 410 } 411 if (index == null) { 412 property = property.get(segment); 413 if (property == null) { 414 throw new PropertyNotFoundException(path.toString(), "segment " + segments[i] 415 + " cannot be resolved"); 416 } 417 } else { 418 property = property.get(index); 419 } 420 } 421 return property; 422 } 423 424 @Override 425 public Serializable normalize(Object value) throws PropertyConversionException { 426 if (isNormalized(value)) { 427 return (Serializable) value; 428 } 429 throw new PropertyConversionException(value.getClass(), Serializable.class, getPath()); 430 } 431 432 @Override 433 public boolean isNormalized(Object value) { 434 return value == null || value instanceof Serializable; 435 } 436 437 @Override 438 public <T> T convertTo(Serializable value, Class<T> toType) throws PropertyConversionException { 439 // TODO FIXME XXX make it abstract at this level 440 throw new UnsupportedOperationException("Not implemented"); 441 } 442 443 @Override 444 public boolean validateType(Class<?> type) { 445 return true; // TODO XXX FIXME 446 } 447 448 @Override 449 public Object newInstance() { 450 return null; // TODO XXX FIXME 451 } 452 453 @Override 454 public String toString() { 455 return getClass().getSimpleName() + '(' + getPath() + ')'; 456 } 457 458 @Override 459 public PropertyObjectResolver getObjectResolver() { 460 ObjectResolver resolver = getType().getObjectResolver(); 461 if (resolver != null) { 462 return new PropertyObjectResolverImpl(this, resolver); 463 } 464 return null; 465 } 466 467 @Override 468 public boolean isForceDirty() { 469 return forceDirty; 470 } 471 472 @Override 473 public void setForceDirty(boolean forceDirty) { 474 this.forceDirty = forceDirty; 475 } 476 477}