| /***************************************************************** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| ****************************************************************/ |
| |
| package org.apache.cayenne; |
| |
| import org.apache.cayenne.exp.path.CayennePath; |
| import org.apache.cayenne.map.DbAttribute; |
| import org.apache.cayenne.map.DbJoin; |
| import org.apache.cayenne.map.DbRelationship; |
| import org.apache.cayenne.map.EmbeddedAttribute; |
| import org.apache.cayenne.map.EntityResolver; |
| import org.apache.cayenne.map.ObjAttribute; |
| import org.apache.cayenne.map.ObjEntity; |
| import org.apache.cayenne.map.ObjRelationship; |
| import org.apache.cayenne.reflect.ClassDescriptor; |
| import org.apache.cayenne.reflect.PropertyDescriptor; |
| import org.apache.cayenne.reflect.PropertyUtils; |
| import org.apache.cayenne.reflect.ToManyMapProperty; |
| import org.apache.cayenne.validation.BeanValidationFailure; |
| import org.apache.cayenne.validation.ValidationFailure; |
| import org.apache.cayenne.validation.ValidationResult; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.lang.reflect.Array; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Base implementation of {@link Persistent}, have no assumption about how data is actually stored. |
| * Provides implementation of properties declared in Persistent interface. |
| * <p> |
| * Three variants are currently supported: |
| * <ul> |
| * <li> field based storage, e.g. each entity class will directly define fields to store data |
| * <li> {@link Map} based storage, e.g. values will be stored in general Map ({@link GenericPersistentObject}) |
| * <li> mixed fields and generic Map to store runtime attributes ({@link HybridPersistentObject}) |
| * </ul> |
| * <p> |
| * This class can be used directly as superclass for field-based data objects. |
| * <p> |
| * To create own implementation of {@link Persistent} with custom field storage logic it is enough |
| * to implement {@link #readPropertyDirectly(String)} and {@link #writePropertyDirectly(String, Object)} methods |
| * and serialization support if needed (helper methods {@link #writeState(ObjectOutputStream)} |
| * and {@link #readState(ObjectInputStream)} are provided). |
| * <h1>POJO Note</h1> |
| * <p> |
| * If having PersistentObject as a superclass presents a problem in an application, source |
| * code of this class can be copied verbatim to a custom class generation template. |
| * Desired superclass can be set in CayenneModeler. |
| * </p> |
| * |
| * @since 1.2 |
| */ |
| public abstract class PersistentObject implements Persistent, Validating { |
| |
| protected ObjectId objectId; |
| protected int persistenceState; |
| protected transient ObjectContext objectContext; |
| protected long snapshotVersion = DEFAULT_VERSION; |
| |
| /** |
| * Creates a new transient object. |
| */ |
| public PersistentObject() { |
| this.persistenceState = PersistenceState.TRANSIENT; |
| } |
| |
| public int getPersistenceState() { |
| return persistenceState; |
| } |
| |
| public ObjectContext getObjectContext() { |
| return objectContext; |
| } |
| |
| /** |
| * @since 1.2 |
| */ |
| @Override |
| public void setObjectContext(ObjectContext objectContext) { |
| this.objectContext = objectContext; |
| |
| if (objectContext == null) { |
| this.persistenceState = PersistenceState.TRANSIENT; |
| } |
| } |
| |
| public ObjectId getObjectId() { |
| return objectId; |
| } |
| |
| public void setObjectId(ObjectId objectId) { |
| this.objectId = objectId; |
| } |
| |
| /** |
| * Returns a map key for a given to-many map relationship and a target object. |
| * |
| * @since 3.0 |
| */ |
| protected Object getMapKey(String relationshipName, Object value) { |
| |
| EntityResolver resolver = objectContext.getEntityResolver(); |
| ClassDescriptor descriptor = resolver |
| .getClassDescriptor(objectId.getEntityName()); |
| |
| if (descriptor == null) { |
| throw new IllegalStateException("Persistent's entity is unmapped, objectId: " |
| + objectId); |
| } |
| |
| PropertyDescriptor property = descriptor.getProperty(relationshipName); |
| if (property instanceof ToManyMapProperty) { |
| return ((ToManyMapProperty) property).getMapKey(value); |
| } |
| |
| throw new IllegalArgumentException("Relationship '" |
| + relationshipName |
| + "' is not a to-many Map"); |
| } |
| |
| @Override |
| public Object readPropertyDirectly(String propName) { |
| // return null by default to keep compatibility with pre 4.1 versions |
| return null; |
| } |
| |
| @Override |
| public void writePropertyDirectly(String propName, Object val) { |
| throw new IllegalArgumentException("Unknown property: " + propName); |
| } |
| |
| protected void beforePropertyRead(String propName) { |
| if (objectContext != null) { |
| // will resolve faults ourselves below as checking class descriptors |
| // for the "lazyFaulting" flag is inefficient. Passing "false" here to |
| // suppress fault processing |
| objectContext.prepareForAccess(this, propName, false); |
| } |
| } |
| |
| protected void beforePropertyWrite(String propName, Object oldValue, Object newValue) { |
| if (objectContext != null) { |
| objectContext.prepareForAccess(this, propName, false); |
| objectContext.propertyChanged(this, propName, oldValue, newValue); |
| } |
| } |
| |
| @Override |
| public Object readProperty(String propertyName) { |
| beforePropertyRead(propertyName); |
| |
| Object object = readPropertyDirectly(propertyName); |
| |
| if (object instanceof Fault) { |
| object = ((Fault) object).resolveFault(this, propertyName); |
| writePropertyDirectly(propertyName, object); |
| } |
| |
| return object; |
| } |
| |
| /** |
| * Returns a value of the property identified by a property path. Supports |
| * reading both mapped and unmapped properties. Unmapped properties are |
| * accessed in a manner consistent with JavaBeans specification. |
| * <p> |
| * Property path (or nested property) is a dot-separated path used to |
| * traverse object relationships until the final object is found. If a null |
| * object found while traversing path, null is returned. If a list is |
| * encountered in the middle of the path, CayenneRuntimeException is thrown. |
| * Unlike {@link #readPropertyDirectly(String)}, this method will resolve an |
| * object if it is HOLLOW. |
| * <p> |
| * Examples: |
| * </p> |
| * <ul> |
| * <li>Read this object property:<br> |
| * <code>String name = (String)artist.readNestedProperty("name");</code><br> |
| * <br> |
| * </li> |
| * <li>Read an object related to this object:<br> |
| * <code>Gallery g = (Gallery)paintingInfo.readNestedProperty("toPainting.toGallery");</code> |
| * <br> |
| * <br> |
| * </li> |
| * <li>Read a property of an object related to this object: <br> |
| * <code>String name = (String)painting.readNestedProperty("toArtist.artistName");</code> |
| * <br> |
| * <br> |
| * </li> |
| * <li>Read to-many relationship list:<br> |
| * <code>List exhibits = (List)painting.readNestedProperty("toGallery.exhibitArray");</code> |
| * <br> |
| * <br> |
| * </li> |
| * <li>Read to-many relationship in the middle of the path:<br> |
| * <code>List<String> names = (List<String>)artist.readNestedProperty("paintingArray.paintingName");</code> |
| * <br> |
| * <br> |
| * </li> |
| * </ul> |
| * |
| * @since 1.0.5 |
| */ |
| @Override |
| public Object readNestedProperty(String path) { |
| return readNestedProperty(CayennePath.of(path)); |
| } |
| |
| /** |
| * @inheritDoc |
| */ |
| @Override |
| public Object readNestedProperty(CayennePath path) { |
| if ((null == path) || path.isEmpty()) { |
| throw new IllegalArgumentException("the path must be supplied in order to lookup a nested property"); |
| } |
| |
| String firstSegment = path.first().value(); |
| Object property = readSimpleProperty(firstSegment); |
| if (property == null) { |
| return null; |
| } |
| |
| if (path.length() == 1) { |
| return property; |
| } |
| |
| CayennePath pathRemainder = path.tail(1); |
| if (property instanceof Persistent) { |
| return ((Persistent) property).readNestedProperty(pathRemainder); |
| } else { |
| return Cayenne.readNestedProperty(property, pathRemainder); |
| } |
| } |
| |
| Object readSimpleProperty(String property) { |
| |
| // side effect - resolves HOLLOW object |
| Object object = readProperty(property); |
| |
| // if a null value is returned, there is still a chance to |
| // find a non-persistent property via reflection |
| if (object == null) { |
| object = PropertyUtils.getProperty(this, property); |
| } |
| |
| return object; |
| } |
| |
| @Override |
| public void writeProperty(String propName, Object val) { |
| Object oldValue = readPropertyDirectly(propName); |
| beforePropertyWrite(propName, oldValue, val); |
| |
| writePropertyDirectly(propName, val); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public void removeToManyTarget(String relName, Persistent value, boolean setReverse) { |
| |
| // Now do the rest of the normal handling (regardless of whether it was |
| // flattened or not) |
| Object holder = readProperty(relName); |
| |
| // call 'propertyChanged' AFTER readProperty as readProperty ensures |
| // that this object fault is resolved |
| objectContext.propertyChanged(this, relName, value, null); |
| |
| // TODO: andrus 8/20/2007 - can we optimize this somehow, avoiding type checking?? |
| if (holder instanceof Collection) { |
| ((Collection<Object>) holder).remove(value); |
| } else if (holder instanceof Map) { |
| ((Map<Object, Object>) holder).remove(getMapKey(relName, value)); |
| } |
| |
| if (value != null && setReverse) { |
| unsetReverseRelationship(relName, value); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public void addToManyTarget(String relName, Persistent value, boolean setReverse) { |
| if (value == null) { |
| throw new NullPointerException("Attempt to add null target Persistent."); |
| } |
| |
| willConnect(relName, value); |
| |
| // Now do the rest of the normal handling (regardless of whether it was |
| // flattened or not) |
| Object holder = readProperty(relName); |
| |
| // call 'propertyChanged' AFTER readProperty as readProperty ensures |
| // that this object fault is resolved |
| objectContext.propertyChanged(this, relName, null, value); |
| |
| // TODO: andrus 8/20/2007 - can we optimize this somehow, avoiding type checking?? |
| if (holder instanceof Collection) { |
| ((Collection<Object>) holder).add(value); |
| } else if (holder instanceof Map) { |
| ((Map<Object, Object>) holder).put(getMapKey(relName, value), value); |
| } |
| |
| if (setReverse) { |
| setReverseRelationship(relName, value); |
| } |
| } |
| |
| /** |
| * Sets the relationships to the specified <code>Persistent</code> objects. |
| * |
| * <p> |
| * New relationships will be created with |
| * {@link #addToManyTarget(String, org.apache.cayenne.Persistent, boolean)}, |
| * already established relationships stay untouched. Missing relationships |
| * will be removed with |
| * {@link #removeToManyTarget(String, org.apache.cayenne.Persistent, boolean)} |
| * and returned as List. You may delete them manually. |
| * </p> |
| * |
| * <p> |
| * Notice: Moving an object relationship to another object, is still needing |
| * an manually "unregister" from the first object by |
| * {@link #removeToManyTarget(String, org.apache.cayenne.Persistent, boolean)} |
| * </p> |
| * |
| * @param relName name of the relation |
| * @param values <code>Persistent</code> objects of this |
| * <code>Collection</code> are set to the object. No changes will |
| * be made to the the <code>Collection</code>, a copy is used. It |
| * is safe to pass a persisted <code>Collection</code> of another |
| * object. |
| * @param setReverse update reverse relationships |
| * @return <code>List<? extends Persistent></code> of unrelated Persistent objects. |
| * If no relationship was removed an empty List is returned. |
| * @throws IllegalArgumentException if no relationship could be read by relName, or if the passed |
| * <code>Collection</code> is null. To clear all relationships |
| * use an empty <code>Collection</code> |
| * @throws UnsupportedOperationException if the relation Collection Type is neither |
| * <code>java.util.Collection</code> nor |
| * <code>java.util.Map</code> |
| * @since 4.0 |
| */ |
| @SuppressWarnings("unchecked") |
| public List<? extends Persistent> setToManyTarget(String relName, Collection<? extends Persistent> values, |
| boolean setReverse) { |
| if (values == null) { |
| throw new IllegalArgumentException("values Collection is null. To clear all relationships use an empty Collection"); |
| } |
| |
| Object property = readProperty(relName); |
| if (property == null) { |
| throw new IllegalArgumentException("unknown relName " + relName); |
| } |
| |
| Collection<Persistent> old; |
| if (property instanceof Map) { |
| old = ((Map<?, Persistent>) property).values(); |
| } else if (property instanceof Collection) { |
| old = (Collection<Persistent>) property; |
| } else { |
| throw new UnsupportedOperationException("setToManyTarget operates only with Map or Collection types"); |
| } |
| |
| // operate on a copy of passed collection |
| values = new ArrayList<>(values); |
| |
| List<Persistent> removedObjects = new ArrayList<>(); |
| |
| // remove all relationships, which are missing in passed collection |
| Persistent[] oldValues = old.toArray(new Persistent[0]); |
| for (Persistent obj : oldValues) { |
| if (!values.contains(obj)) { |
| removeToManyTarget(relName, obj, setReverse); |
| // collect objects whose relationship was removed |
| removedObjects.add(obj); |
| } |
| } |
| |
| // don't add elements which are already present |
| for (Persistent obj : old) { |
| values.remove(obj); |
| } |
| |
| // add new elements |
| for (Persistent obj : values) { |
| addToManyTarget(relName, obj, setReverse); |
| } |
| |
| return removedObjects; |
| } |
| |
| @Override |
| public void setToOneTarget(String relationshipName, Persistent value, boolean setReverse) { |
| |
| willConnect(relationshipName, value); |
| |
| Object oldTarget = readProperty(relationshipName); |
| if (oldTarget == value) { |
| return; |
| } |
| |
| getObjectContext().propertyChanged(this, relationshipName, oldTarget, value); |
| |
| if (setReverse) { |
| // unset old reverse relationship |
| if (oldTarget instanceof Persistent) { |
| unsetReverseRelationship(relationshipName, (Persistent) oldTarget); |
| } |
| |
| // set new reverse relationship |
| if (value != null) { |
| setReverseRelationship(relationshipName, value); |
| } |
| } |
| |
| // readProperty will call this |
| //objectContext.prepareForAccess(this, relationshipName, false); |
| writePropertyDirectly(relationshipName, value); |
| } |
| |
| /** |
| * Called before establishing a relationship with another object. Applies |
| * "persistence by reachability" logic, pulling one of the two objects to a |
| * DataConext of another object in case one of the objects is transient. If |
| * both objects are persistent, and they don't have the same DataContext, |
| * CayenneRuntimeException is thrown. |
| * |
| * @since 1.2 |
| */ |
| protected void willConnect(String relationshipName, Persistent object) { |
| // first handle most common case - both objects are in the same |
| // ObjectContext or target is null |
| if (object == null || this.getObjectContext() == object.getObjectContext()) { |
| return; |
| } else if (this.getObjectContext() == null && object.getObjectContext() != null) { |
| object.getObjectContext().registerNewObject(this); |
| } else if (this.getObjectContext() != null && object.getObjectContext() == null) { |
| this.getObjectContext().registerNewObject(object); |
| } else { |
| throw new CayenneRuntimeException("Cannot set object as destination of relationship %s" |
| + " because it is in a different ObjectContext", relationshipName); |
| } |
| } |
| |
| /** |
| * Initializes reverse relationship from object <code>val</code> to this |
| * object. |
| * |
| * @param relName name of relationship from this object to <code>val</code>. |
| */ |
| protected void setReverseRelationship(String relName, Persistent val) { |
| ObjRelationship rel = objectContext.getEntityResolver().getObjEntity(objectId.getEntityName()) |
| .getRelationship(relName); |
| ObjRelationship revRel = rel.getReverseRelationship(); |
| if (revRel != null) { |
| Object oldTarget = val.readProperty(revRel.getName()); |
| if (oldTarget != val && oldTarget instanceof Persistent && val instanceof PersistentObject) { |
| ((PersistentObject)val).unsetReverseRelationship(revRel.getName(), (Persistent) oldTarget); |
| } |
| if (revRel.isToMany()) { |
| val.addToManyTarget(revRel.getName(), this, false); |
| } else { |
| val.setToOneTarget(revRel.getName(), this, false); |
| } |
| } |
| } |
| |
| /** |
| * Removes current object from reverse relationship of object |
| * <code>val</code> to this object. |
| */ |
| protected void unsetReverseRelationship(String relName, Persistent val) { |
| |
| EntityResolver resolver = objectContext.getEntityResolver(); |
| ObjEntity entity = resolver.getObjEntity(objectId.getEntityName()); |
| |
| if (entity == null) { |
| throw new IllegalStateException("Persistent's entity is unmapped, objectId: " + objectId); |
| } |
| |
| ObjRelationship rel = entity.getRelationship(relName); |
| ObjRelationship revRel = rel.getReverseRelationship(); |
| if (revRel != null) { |
| if (revRel.isToMany()) { |
| val.removeToManyTarget(revRel.getName(), this, false); |
| } else { |
| val.setToOneTarget(revRel.getName(), null, false); |
| } |
| } |
| } |
| |
| @Override |
| public void setPersistenceState(int persistenceState) { |
| this.persistenceState = persistenceState; |
| } |
| |
| /** |
| * @since 1.1 |
| */ |
| @Override |
| public long getSnapshotVersion() { |
| return snapshotVersion; |
| } |
| |
| /** |
| * @since 1.1 |
| */ |
| @Override |
| public void setSnapshotVersion(long snapshotVersion) { |
| this.snapshotVersion = snapshotVersion; |
| } |
| |
| /** |
| * Performs property validation of the object, appending any validation |
| * failures to the provided validationResult object. This method is invoked |
| * from "validateFor.." before committing a NEW or MODIFIED object to the |
| * database. Validation includes checking for null values and value sizes. |
| * PersistentObject subclasses may override this method, calling super. |
| * |
| * @since 1.1 |
| */ |
| protected void validateForSave(ValidationResult validationResult) { |
| |
| ObjEntity objEntity = getObjectContext().getEntityResolver().getObjEntity(this); |
| if (objEntity == null) { |
| throw new CayenneRuntimeException("No ObjEntity mapping found for Persistent %s", getClass().getName()); |
| } |
| |
| // validate mandatory attributes |
| |
| Map<String, ValidationFailure> failedDbAttributes = null; |
| |
| for (ObjAttribute next : objEntity.getAttributes()) { |
| // TODO: andrus, 2/20/2007 - handle embedded attribute |
| if (next instanceof EmbeddedAttribute) { |
| continue; |
| } |
| |
| DbAttribute dbAttribute = next.getDbAttribute(); |
| if (dbAttribute == null) { |
| throw new CayenneRuntimeException("ObjAttribute '%s" |
| + "' does not have a corresponding DbAttribute", next.getName()); |
| } |
| |
| // pk may still be generated |
| if (dbAttribute.isPrimaryKey()) { |
| continue; |
| } |
| |
| Object value = this.readPropertyDirectly(next.getName()); |
| if (dbAttribute.isMandatory()) { |
| ValidationFailure failure = BeanValidationFailure.validateNotNull(this, next.getName(), value); |
| |
| if (failure != null) { |
| |
| if (failedDbAttributes == null) { |
| failedDbAttributes = new HashMap<>(); |
| } |
| |
| failedDbAttributes.put(dbAttribute.getName(), failure); |
| continue; |
| } |
| } |
| |
| // validate length |
| if (value != null && dbAttribute.getMaxLength() > 0) { |
| if (value.getClass().isArray()) { |
| int len = Array.getLength(value); |
| if (len > dbAttribute.getMaxLength()) { |
| String message = "\"" + next.getName() + "\" exceeds maximum allowed length (" |
| + dbAttribute.getMaxLength() + " bytes): " + len; |
| validationResult.addFailure(new BeanValidationFailure(this, next.getName(), message)); |
| } |
| } else if (value instanceof CharSequence) { |
| int len = ((CharSequence) value).length(); |
| if (len > dbAttribute.getMaxLength()) { |
| String message = "\"" + next.getName() + "\" exceeds maximum allowed length (" |
| + dbAttribute.getMaxLength() + " chars): " + len; |
| validationResult.addFailure(new BeanValidationFailure(this, next.getName(), message)); |
| } |
| } |
| } |
| } |
| |
| // validate mandatory relationships |
| for (final ObjRelationship relationship : objEntity.getRelationships()) { |
| |
| List<DbRelationship> dbRels = relationship.getDbRelationships(); |
| if (dbRels.isEmpty()) { |
| continue; |
| } |
| |
| // skip db relationships that we can't validate or that can't be invalid here |
| // can't handle paths longer than two db relationships |
| // see ObjRelationship.recalculateReadOnlyValue() for more info |
| if (relationship.isSourceIndependentFromTargetChange()) { |
| continue; |
| } |
| |
| // if db relationship is not based on a PK and is based on mandatory |
| // attributes, see if we have a target object set |
| // relationship will be validated only if all db path has mandatory |
| // db relationships |
| boolean validate = true; |
| for (DbRelationship dbRelationship : dbRels) { |
| for (DbJoin join : dbRelationship.getJoins()) { |
| DbAttribute source = join.getSource(); |
| if (source.isMandatory()) { |
| // clear attribute failures... |
| if (failedDbAttributes != null && !failedDbAttributes.isEmpty()) { |
| failedDbAttributes.remove(source.getName()); |
| } |
| } else { |
| // do not validate if the relation is based on |
| // multiple keys with some that can be nullable. |
| validate = false; |
| } |
| } |
| } |
| |
| if (validate) { |
| Object value = this.readPropertyDirectly(relationship.getName()); |
| ValidationFailure failure = BeanValidationFailure.validateNotNull(this, relationship.getName(), value); |
| |
| if (failure != null) { |
| validationResult.addFailure(failure); |
| } |
| } |
| } |
| |
| // deal with previously found attribute failures... |
| if (failedDbAttributes != null && !failedDbAttributes.isEmpty()) { |
| for (ValidationFailure failure : failedDbAttributes.values()) { |
| validationResult.addFailure(failure); |
| } |
| } |
| } |
| |
| /** |
| * Calls {@link #validateForSave(ValidationResult)}. PersistentObject |
| * subclasses may override it providing validation logic that should be |
| * executed for the newly created objects before saving them. |
| * |
| * @since 1.1 |
| */ |
| @Override |
| public void validateForInsert(ValidationResult validationResult) { |
| validateForSave(validationResult); |
| } |
| |
| /** |
| * Calls {@link #validateForSave(ValidationResult)}. PersistentObject |
| * subclasses may override it providing validation logic that should be |
| * executed for the modified objects before saving them. |
| * |
| * @since 1.1 |
| */ |
| @Override |
| public void validateForUpdate(ValidationResult validationResult) { |
| validateForSave(validationResult); |
| } |
| |
| /** |
| * This implementation does nothing. PersistentObject subclasses may |
| * override it providing validation logic that should be executed for the |
| * deleted objects before committing them. |
| * |
| * @since 1.1 |
| */ |
| @Override |
| public void validateForDelete(ValidationResult validationResult) { |
| // does nothing |
| } |
| |
| /** |
| * Serialization support. |
| * Will write down persistenceState and objectId, delegating data serialization down to sub-classes. |
| */ |
| private void writeObject(ObjectOutputStream out) throws IOException { |
| writeSerialized(out); |
| } |
| |
| /** |
| * Serialization support. |
| * Will read persistenceState and objectId, delegating data serialization down to sub-classes. |
| */ |
| private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { |
| readSerialized(in); |
| } |
| |
| protected void writeSerialized(ObjectOutputStream out) throws IOException { |
| out.writeInt(persistenceState); |
| out.writeObject(objectId); |
| |
| if (persistenceState == PersistenceState.COMMITTED |
| || persistenceState == PersistenceState.HOLLOW) { |
| return; |
| } |
| |
| writeState(out); |
| } |
| |
| protected void readSerialized(ObjectInputStream in) throws IOException, ClassNotFoundException { |
| this.persistenceState = in.readInt(); |
| this.objectId = (ObjectId) in.readObject(); |
| |
| if (persistenceState == PersistenceState.COMMITTED |
| || persistenceState == PersistenceState.HOLLOW) { |
| persistenceState = PersistenceState.HOLLOW; |
| return; |
| } |
| |
| readState(in); |
| } |
| |
| protected void writeState(ObjectOutputStream out) throws IOException { |
| // no additional info for base class |
| } |
| |
| protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { |
| // no additional info for base class |
| } |
| |
| /** |
| * A variation of "toString" method, that may be more efficient in some |
| * cases. For example when printing a list of objects into the same String. |
| */ |
| public StringBuffer toStringBuffer(StringBuffer buffer, boolean fullDesc) { |
| String id = (objectId != null) ? objectId.toString() : "<no id>"; |
| String state = PersistenceState.persistenceStateName(persistenceState); |
| |
| buffer.append('{').append(id).append("; ").append(state).append("; "); |
| |
| if (fullDesc) { |
| appendProperties(buffer); |
| } |
| |
| buffer.append("}"); |
| return buffer; |
| } |
| |
| protected void appendProperties(StringBuffer buffer) { |
| } |
| |
| @Override |
| public String toString() { |
| return toStringBuffer(new StringBuffer(), true).toString(); |
| } |
| } |