001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019
020package org.apache.isis.core.metamodel.adapter;
021
022import static org.apache.isis.core.metamodel.adapter.ResolveState.RepresentsPersistent.DOES_NOT_REPRESENT_PERSISTENT;
023import static org.apache.isis.core.metamodel.adapter.ResolveState.RepresentsPersistent.REPRESENTS_PERSISTENT;
024import static org.apache.isis.core.metamodel.adapter.ResolveState.RespondsToChanges.DOES_NOT_RESPOND_TO_CHANGES;
025import static org.apache.isis.core.metamodel.adapter.ResolveState.RespondsToChanges.RESPONDS_TO_CHANGES;
026import static org.apache.isis.core.metamodel.adapter.ResolveState.TransitionFrom.CANNOT_TRANSITION_FROM;
027import static org.apache.isis.core.metamodel.adapter.ResolveState.TransitionFrom.CAN_TRANSITION_FROM;
028
029import java.util.Arrays;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Map;
034
035import com.google.common.collect.Maps;
036
037public final class ResolveState {
038    private static final Map<String, ResolveState> statesByName = Maps.newHashMap();
039
040    static enum TransitionFrom {
041        CAN_TRANSITION_FROM, CANNOT_TRANSITION_FROM
042    }
043
044    static enum RespondsToChanges {
045        RESPONDS_TO_CHANGES, DOES_NOT_RESPOND_TO_CHANGES
046    }
047
048    static enum RepresentsPersistent {
049        REPRESENTS_PERSISTENT, DOES_NOT_REPRESENT_PERSISTENT
050    }
051
052    /**
053     * When first instantiated by <tt>PojoAdapterFactory</tt>.
054     */
055    public static final ResolveState NEW       = new ResolveState("New",       "N~~", null,      CANNOT_TRANSITION_FROM, DOES_NOT_RESPOND_TO_CHANGES, DOES_NOT_REPRESENT_PERSISTENT);
056    public static final ResolveState TRANSIENT = new ResolveState("Transient", "T~~", null,      CANNOT_TRANSITION_FROM, DOES_NOT_RESPOND_TO_CHANGES, DOES_NOT_REPRESENT_PERSISTENT);
057    public static final ResolveState GHOST     = new ResolveState("Ghost",     "PG~", null,      CAN_TRANSITION_FROM,    RESPONDS_TO_CHANGES,         REPRESENTS_PERSISTENT);
058    public static final ResolveState RESOLVED  = new ResolveState("Resolved",  "PR~", null,      CAN_TRANSITION_FROM,    RESPONDS_TO_CHANGES,         REPRESENTS_PERSISTENT);
059    public static final ResolveState RESOLVING = new ResolveState("Resolving", "Pr~", RESOLVED,  CANNOT_TRANSITION_FROM, DOES_NOT_RESPOND_TO_CHANGES, REPRESENTS_PERSISTENT);
060    public static final ResolveState UPDATING  = new ResolveState("Updating",  "PU~", RESOLVED,  CANNOT_TRANSITION_FROM, DOES_NOT_RESPOND_TO_CHANGES, REPRESENTS_PERSISTENT);
061    public static final ResolveState DESTROYED = new ResolveState("Destroyed", "D~~", null,      CANNOT_TRANSITION_FROM, RESPONDS_TO_CHANGES,         DOES_NOT_REPRESENT_PERSISTENT);
062    public static final ResolveState VALUE     = new ResolveState("Value",     "V~~", null,      CANNOT_TRANSITION_FROM, RESPONDS_TO_CHANGES,         DOES_NOT_REPRESENT_PERSISTENT);
063
064    // 20120709: used only in <tt>Memento</tt>, when recreating a transient object.
065    // however, analysis is that could equally set to TRANSIENT, rendering this state
066    // surplus to requirements.
067    // public static final ResolveState SERIALIZING_TRANSIENT = new ResolveState("Serializing Transient", "T~S", TRANSIENT, CANNOT_TRANSITION_FROM, DOES_NOT_RESPOND_TO_CHANGES, REPRESENTS_TRANSIENT,         DOES_NOT_REPRESENT_PERSISTENT);
068
069    // no longer seem to be used
070
071    // public static final ResolveState PART_RESOLVED = new
072    // ResolveState("Part Resolved", "Pr~~", null, RESOLVABLE_FROM,
073    // NOT_RESOLVABLE_INTO, RESPONDS_TO_CHANGES, DOES_NOT_REPRESENT_TRANSIENT,
074    // REPRESENTS_PERSISTENT, DOES_NOT_REPRESENT_RESOLVING, COULD_RESOLVE);
075    // public static final ResolveState RESOLVING_PART = new
076    // ResolveState("Resolving Part", "P~r~", PART_RESOLVED,
077    // NOT_RESOLVABLE_FROM, RESOLVABLE_INTO, DOES_NOT_RESPOND_TO_CHANGES,
078    // DOES_NOT_REPRESENT_TRANSIENT, REPRESENTS_PERSISTENT,
079    // REPRESENTS_RESOLVING, COULD_RESOLVE);
080
081    // no longer appear to be needed following the removal of remoting support.
082
083    // public static final ResolveState SERIALIZING_GHOST = new
084    // ResolveState("Serializing Ghost", "PG~S", GHOST, NOT_RESOLVABLE_FROM,
085    // NOT_RESOLVABLE_INTO, DOES_NOT_RESPOND_TO_CHANGES,
086    // DOES_NOT_REPRESENT_TRANSIENT, REPRESENTS_PERSISTENT,
087    // DOES_NOT_REPRESENT_RESOLVING, COULD_RESOLVE);
088    // public static final ResolveState SERIALIZING_PART_RESOLVED = new
089    // ResolveState("Serializing Part Resolved", "Pr~S", PART_RESOLVED,
090    // NOT_RESOLVABLE_FROM, NOT_RESOLVABLE_INTO, DOES_NOT_RESPOND_TO_CHANGES,
091    // DOES_NOT_REPRESENT_TRANSIENT, REPRESENTS_PERSISTENT,
092    // DOES_NOT_REPRESENT_RESOLVING,
093    // COULD_RESOLVE);
094
095    // 20120709: only used in <tt>DefaultPersistAlgorithm</tt>
096    // able to remove because, after refactoring simplifications, ended up as equivalent to UPDATING.
097    // public static final ResolveState SERIALIZING_RESOLVED  = new ResolveState("Serializing Resolved",   "PRS", RESOLVED,  CANNOT_TRANSITION_FROM, DOES_NOT_RESPOND_TO_CHANGES, DOES_NOT_REPRESENT_TRANSIENT, REPRESENTS_PERSISTENT);
098
099
100    /**
101     * These cannot be passed into the constructor because cannot reference an
102     * instance until it has been declared.
103     */
104    public static Map<ResolveState, ResolveState[]> changeToStatesByState = new HashMap<ResolveState, ResolveState[]>() {
105        private static final long serialVersionUID = 1L;
106        {
107            // previously also RESOLVING_PART and SERIALIZING_GHOST
108            put(GHOST, new ResolveState[] { DESTROYED, RESOLVING, UPDATING });
109            put(NEW, new ResolveState[] { TRANSIENT, GHOST, VALUE });
110            // previously also SERIALIZING_TRANSIENT
111            put(TRANSIENT, new ResolveState[] { RESOLVED });
112            put(RESOLVING, new ResolveState[] { RESOLVED });
113            // previously also SERIALIZING_RESOLVED
114            put(RESOLVED, new ResolveState[] { GHOST, UPDATING, DESTROYED });
115            //put(SERIALIZING_RESOLVED, new ResolveState[] { RESOLVED });
116            //put(SERIALIZING_TRANSIENT, new ResolveState[] { TRANSIENT });
117            put(UPDATING, new ResolveState[] { RESOLVED });
118            put(DESTROYED, new ResolveState[] {});
119            put(VALUE, new ResolveState[] {});
120
121            // put(PART_RESOLVED, new ResolveState[] { RESOLVING , UPDATING,
122            // DESTROYED , RESOLVING_PART, SERIALIZING_PART_RESOLVED });
123            // put(RESOLVING_PART, new ResolveState[] { PART_RESOLVED, RESOLVED
124            // });
125            // put(SERIALIZING_GHOST, new ResolveState[] { GHOST });
126            // put(SERIALIZING_PART_RESOLVED, new ResolveState[] { PART_RESOLVED
127            // });
128
129        }
130    };
131
132    private final String code;
133    private final ResolveState endState;
134    private final String name;
135    private final TransitionFrom transitionFrom;
136    private final RespondsToChanges respondsToChanges;
137    private final RepresentsPersistent representsPersistent;
138    private HashSet<ResolveState> changeToStates;
139
140    private ResolveState(final String name, final String code, final ResolveState endState, final TransitionFrom transitionFrom, final RespondsToChanges respondsToChanges, final RepresentsPersistent representsPersistent) {
141        this.name = name;
142        this.code = code;
143        this.endState = endState;
144        this.transitionFrom = transitionFrom;
145        this.respondsToChanges = respondsToChanges;
146        this.representsPersistent = representsPersistent;
147        statesByName.put(name, this);
148    }
149
150    /**
151     * Four character representation of the state.
152     * 
153     * <p>
154     * The format is <tt>XYZ</tt> where:
155     * <ul>
156     * <li><tt>X</tt> is transient state:
157     * <ul>
158     * <li>N</li> for <b>N</b>ew
159     * <li>T</li> for <b>T</b>ransient
160     * <li>P</li> for <b>P</b>ersistent
161     * <li>D</li> for <b>D</b>estroyed
162     * <li>V</li> for <b>V</b>alue
163     * </ul>
164     * </li>
165     * <li><tt>Y</tt> (for persistent only) is the resolve state:
166     * <ul>
167     * <li>G</li> for <b>G</b>host
168     * <li>R</li> for <b>R</b>esolved
169     * <li>r</li> for <b>r</b>esolving
170     * <li>~</li> if not persistent
171     * </ul>
172     * </li>
173     * <li><tt>Z</tt> (for non-standalone, not resolving, not updating, not
174     * destroyed) is the serialization state:
175     * <ul>
176     * <li>~</li> not serializing
177     * <li>S</li> is serializing
178     * </ul>
179     * </li>
180     * </ul>
181     */
182    public String code() {
183        return code;
184    }
185
186    public ResolveState getEndState() {
187        return endState;
188    }
189
190    /**
191     * Returns <tt>true</tt> when an object is persistent (except for
192     * {@link #VALUE} adapters).
193     * 
194     * <p>
195     * Always returns <tt>false</tt> for {@link #VALUE}.
196     */
197    public boolean representsPersistent() {
198        return this.representsPersistent == RepresentsPersistent.REPRESENTS_PERSISTENT;
199    }
200
201    /**
202     * As per {@link #isValidToChangeTo(ResolveState)}, but will additionally
203     * return <tt>false</tt> if the current state can never be transitioned from.
204     */
205    public boolean canTransitionToResolving() {
206        if (this.transitionFrom != CAN_TRANSITION_FROM) {
207            return false;
208        } 
209        return isValidToChangeTo(ResolveState.RESOLVING);
210    }
211
212    /**
213     * Returns false while object is having its field set up.
214     */
215    public boolean respondToChangesInPersistentObjects() {
216        return respondsToChanges == RESPONDS_TO_CHANGES;
217    }
218
219    public boolean isNew() {
220        return this == NEW;
221    }
222    
223    public boolean isValue() {
224        return this == VALUE;
225    }
226
227    public boolean isTransient() {
228        return this == TRANSIENT;
229    }
230
231    public boolean isGhost() {
232        return this == GHOST;
233    }
234    
235    public boolean isUpdating() {
236        return this == UPDATING;
237    }
238
239    public boolean isResolved() {
240        return this == RESOLVED;
241    }
242
243    public boolean isResolving() {
244        return this == RESOLVING;
245    }
246    
247    public boolean isDestroyed() {
248        return this == DESTROYED;
249    }
250    
251
252    /**
253     * Determines if the resolved state can be changed from this state to the
254     * specified state. Returns true if the change is valid.
255     */
256    public boolean isValidToChangeTo(final ResolveState nextState) {
257        cacheChangeToStatesIfNecessary();
258        return this.changeToStates.contains(nextState);
259    }
260
261    private void cacheChangeToStatesIfNecessary() {
262        if (this.changeToStates == null) {
263            final List<ResolveState> nextStates = Arrays.asList(changeToStatesByState.get(this));
264            changeToStates = new HashSet<ResolveState>(nextStates);
265        }
266    }
267
268    public String name() {
269        return name;
270    }
271
272    private transient String cachedToString;
273
274    @Override
275    public String toString() {
276        if (cachedToString == null) {
277            final StringBuffer str = new StringBuffer();
278            str.append("ResolveState [name=");
279            str.append(name);
280            str.append(",code=");
281            str.append(code);
282            if (endState != null) {
283                str.append(",endstate=");
284                str.append(endState.name());
285            }
286            str.append("]");
287            cachedToString = str.toString();
288        }
289        return cachedToString;
290    }
291
292}