001: /*
002: * @(#)AbstractEditableIssue.java
003: *
004: * Copyright (C) 2002-2003 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Part of the GroboUtils package at:
009: * http://groboutils.sourceforge.net
010: *
011: * Permission is hereby granted, free of charge, to any person obtaining a
012: * copy of this software and associated documentation files (the "Software"),
013: * to deal in the Software without restriction, including without limitation
014: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
015: * and/or sell copies of the Software, and to permit persons to whom the
016: * Software is furnished to do so, subject to the following conditions:
017: *
018: * The above copyright notice and this permission notice shall be included in
019: * all copies or substantial portions of the Software.
020: *
021: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
022: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
023: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
024: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
025: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
026: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
027: * DEALINGS IN THE SOFTWARE.
028: */
029: package net.sourceforge.groboutils.pmti.v1.defimpl;
030:
031: import net.sourceforge.groboutils.pmti.v1.IIssue;
032: import net.sourceforge.groboutils.pmti.v1.IIssueState;
033: import net.sourceforge.groboutils.pmti.v1.IAttributeSet;
034: import net.sourceforge.groboutils.pmti.v1.IEditableIssue;
035: import net.sourceforge.groboutils.pmti.v1.IEditableIssueState;
036: import net.sourceforge.groboutils.pmti.v1.IEditableAttributeSet;
037: import net.sourceforge.groboutils.pmti.v1.ProblemManagerException;
038:
039: /**
040: * Allows for editing of an issue. The only parts that can't be edited are
041: * the ID and type, since those uniquely identify the issue at hand. Editing
042: * an issue has several constraints that should be known by the user:
043: * <UL>
044: * <LI>
045: * Just like with the <tt>IIssue</tt> instances, the issue being
046: * edited will NOT be real-time updated to reflect the current
047: * tracker state. Currently, the only way to update an issue is by
048: * re-polling the <tt>ProblemManager</tt>. Individual implementations
049: * may provide for alternative means to receive synchronized issues.
050: * </LI>
051: * <LI>
052: * No changes to an editable issue will be committed to the problem
053: * tracker is to call <tt>commit()</tt> on the issue.
054: * </LI>
055: * </UL>
056: *
057: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
058: * @version $Date: 2003/02/10 22:51:57 $
059: * @since July 12, 2002
060: */
061: public abstract class AbstractEditableIssue implements IEditableIssue {
062: private IIssue baseIssue;
063:
064: private String newDesc;
065: private boolean changedDesc;
066:
067: private IIssueState newState;
068: private boolean changedState;
069:
070: private IEditableAttributeSet nextSet;
071:
072: public AbstractEditableIssue(IIssue base) {
073: if (base == null) {
074: throw new IllegalArgumentException("no null arguments");
075: }
076: this .baseIssue = base;
077: }
078:
079: //-------------------------------------------------------------------------
080: // IIssue
081:
082: public String getID() {
083: return this .baseIssue.getID();
084: }
085:
086: public String getType() {
087: return this .baseIssue.getType();
088: }
089:
090: public String getShortDescription() {
091: if (this .changedDesc) {
092: return this .newDesc;
093: } else {
094: return this .baseIssue.getShortDescription();
095: }
096: }
097:
098: public IIssueState getState() {
099: if (this .changedState) {
100: return this .newState;
101: } else {
102: return this .baseIssue.getState();
103: }
104: }
105:
106: public IAttributeSet getAttributes() {
107: return getEditableAttributes();
108: }
109:
110: public abstract IIssue reload() throws ProblemManagerException;
111:
112: //-------------------------------------------------------------------------
113: // IEditableIssue
114:
115: /**
116: *
117: */
118: public void setShortDescription(String desc) {
119: String orig = this .baseIssue.getShortDescription();
120: if (desc == orig || (desc != null && desc.equals(orig))) {
121: // they're the same - ensure the state is correct
122: this .changedDesc = false;
123: } else {
124: // changed
125: this .changedDesc = true;
126: this .newDesc = desc;
127: }
128: }
129:
130: /**
131: * @return <tt>true</tt> if <tt>setShortDescription( String )</tt> was
132: * called with a different description string than the original
133: * issue, otherwise <tt>false</tt>.
134: */
135: public boolean hasShortDescriptionChanged() {
136: return this .changedDesc;
137: }
138:
139: /**
140: * Returns the list of all states that this issue can move to next.
141: * This is part of the workflow logic of the underlying PMT. The returned
142: * states may be safely edited without any affect; the only effect will be
143: * when the state is explicitly set. This will always return, in index 0,
144: * a <b>copy</b> of the current state as editable.
145: */
146: public IEditableIssueState[] getNextStates() {
147: return createNextEditableIssueStates(getState());
148: }
149:
150: /**
151: * Sets the current state. Since there is no getEditableState() method,
152: * use this method if any information in the current state needs to be
153: * updated. You can retrieve the current state as an editable state
154: * using <tt>getNextStates()[0]</tt>, but note that any changes to that
155: * editable version will not affect the tracker's state unless that
156: * editable instance is explicitly set in this method.
157: *
158: * @exception ProblemManagerException if the input state is not a valid
159: * next state.
160: */
161: public void setState(IIssueState state)
162: throws ProblemManagerException {
163: assertStateCanBeNext(state);
164:
165: if (state instanceof IEditableIssueState) {
166: state = createImmutableIssueState((IEditableIssueState) state);
167: }
168: this .newState = state;
169: this .changedState = true;
170: }
171:
172: /**
173: * @return <tt>true</tt> if the <tt>setState( IIssueState )</tt> method
174: * has been invoked and did not throw an exception, otherwise
175: * <tt>false</tt>. Note that even if the set state is an unchanged
176: * version of the current issue's state, this will still return
177: * <tt>true</tt>.
178: */
179: public boolean hasStateChanged() {
180: return this .changedState;
181: }
182:
183: /**
184: * This is a synonymn for <tt>getAttributes()</tt>, but this explicitly
185: * sets the returned value as an editable set, without the need for an
186: * extra cast. The returned attribute set may be safely edited, and
187: * changes there will affect the issue that returned them.
188: */
189: public IEditableAttributeSet getEditableAttributes() {
190: if (this .nextSet == null) {
191: // create the editable set
192: IAttributeSet s = this .baseIssue.getAttributes();
193: this .nextSet = createEditableAttributeSet(s);
194: }
195: return this .nextSet;
196: }
197:
198: /**
199: * Commits all changes from the issue to the tracker.
200: * <P>
201: * In theory, issues should never be removed. However, some systems allow
202: * them to be deleted (say, if there was an accidental creation). In this
203: * case, an <tt>IssueRemovedException</tt> will be thrown.
204: *
205: * @exception ProblemManagerException if there was an underlying tracker
206: * error.
207: */
208: public abstract void commit() throws ProblemManagerException;
209:
210: //-------------------------------------------------------------------------
211: // protected methods
212:
213: /**
214: * Creates a set of editable issue states that can be set as 'next' for
215: * <tt>is</tt>. Note that it is required that the input <tt>is</tt>
216: * <i>must</i> be returned in index 0 of the returned array.
217: */
218: protected abstract IEditableIssueState[] createNextEditableIssueStates(
219: IIssueState is);
220:
221: /**
222: * Creates an editable set of attributes based on the immutable attribute
223: * set. The new set of attributes should accurately reflect the input
224: * attribute set's values.
225: */
226: protected abstract IEditableAttributeSet createEditableAttributeSet(
227: IAttributeSet as);
228:
229: /**
230: * Create a non-editable version of <tt>eis</tt>.
231: */
232: protected abstract IIssueState createImmutableIssueState(
233: IEditableIssueState eis);
234:
235: protected void assertStateCanBeNext(IIssueState state)
236: throws ProblemManagerException {
237: IEditableIssueState[] eis = getNextStates();
238: if (eis == null || eis.length < 1) {
239: throw new IllegalStateException(
240: "Invalid set of next states.");
241: }
242:
243: String name = null;
244: if (state != null) {
245: name = state.getName();
246: }
247:
248: for (int i = 0; i < eis.length; ++i) {
249: if (eis[i] == null) {
250: if (state == null) {
251: // they are the same - null state supported.
252: return;
253: }
254: } else
255: // allow for a null name for the input state,
256: // but the non-null state must have a non-null name.
257: if (eis[i].getName().equals(name)) {
258: // found the requested state in the next state list,
259: // so the requested state is valid.
260: return;
261: }
262: }
263: // never found the requested state in the next state list.
264: throw new ProblemManagerException("State " + state
265: + " cannot be a next state for " + getState());
266: }
267: }
|