001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
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: package org.kuali.kfs.bo;
017:
018: import java.util.Arrays;
019: import java.util.Collections;
020: import java.util.HashMap;
021: import java.util.HashSet;
022: import java.util.List;
023: import java.util.Map;
024: import java.util.Set;
025:
026: import org.kuali.core.util.ObjectUtils;
027: import org.kuali.kfs.context.SpringContext;
028: import org.kuali.module.chart.bo.Account;
029: import org.kuali.module.chart.bo.ObjectCode;
030: import org.kuali.module.financial.service.AccountPresenceService;
031:
032: /**
033: * This class helps implement AccountingLine overrides. It is not persisted itself, but it simplifies working with the persisted
034: * codes. Instances break the code into components. Static methods help with the AccountingLine.
035: */
036: public class AccountingLineOverride {
037:
038: /**
039: * These codes are the way the override is persisted in the AccountingLine.
040: */
041: public static final class CODE { // todo: use JDK 1.5 enum
042: public static final String NONE = "N";
043: public static final String EXPIRED_ACCOUNT = "1";
044: public static final String NON_BUDGETED_OBJECT = "2";
045: public static final String TRANSACTION_EXCEEDS_REMAINING_BUDGET = "3";
046: public static final String EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT = "4";
047: public static final String NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET = "5";
048: public static final String EXPIRED_ACCOUNT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET = "6";
049: public static final String EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET = "7";
050: public static final String NON_FRINGE_ACCOUNT_USED = "8";
051: public static final String EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED = "9";
052: }
053:
054: /**
055: * These are the somewhat independent components of an override.
056: */
057: public static final class COMPONENT { // todo: use JDK 1.5 enum
058: public static final Integer EXPIRED_ACCOUNT = new Integer(1);
059: public static final Integer NON_BUDGETED_OBJECT = new Integer(2);
060: public static final Integer TRANSACTION_EXCEEDS_REMAINING_BUDGET = new Integer(
061: 3);
062: public static final Integer NON_FRINGE_ACCOUNT_USED = new Integer(
063: 8);
064: }
065:
066: /**
067: * The names of the AccountingLine properties that the processForOutput() and determineNeededOverrides() methods use. Callers of
068: * those methods may need to refresh these fields from OJB.
069: */
070: // todo: JDK 1.5 generic List
071: public static final List REFRESH_FIELDS = Collections
072: .unmodifiableList(Arrays.asList(new String[] { "account",
073: "objectCode" }));
074:
075: /**
076: * This holds an instance of every valid override, mapped by code.
077: */
078: private static final Map codeToOverrideMap = new HashMap();
079:
080: /**
081: * This holds an instance of every valid override, mapped by components.
082: */
083: private static final Map componentsToOverrideMap = new HashMap();
084:
085: static {
086: // populate the code map
087: new AccountingLineOverride(CODE.NONE, new Integer[] {});
088: new AccountingLineOverride(CODE.EXPIRED_ACCOUNT,
089: // todo: use JDK 1.5 ... args
090: new Integer[] { COMPONENT.EXPIRED_ACCOUNT });
091: new AccountingLineOverride(CODE.NON_BUDGETED_OBJECT,
092: new Integer[] { COMPONENT.NON_BUDGETED_OBJECT });
093: new AccountingLineOverride(
094: CODE.TRANSACTION_EXCEEDS_REMAINING_BUDGET,
095: new Integer[] { COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET });
096: new AccountingLineOverride(
097: CODE.EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT,
098: new Integer[] { COMPONENT.EXPIRED_ACCOUNT,
099: COMPONENT.NON_BUDGETED_OBJECT });
100: new AccountingLineOverride(
101: CODE.NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET,
102: new Integer[] { COMPONENT.NON_BUDGETED_OBJECT,
103: COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET });
104: new AccountingLineOverride(
105: CODE.EXPIRED_ACCOUNT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET,
106: new Integer[] { COMPONENT.EXPIRED_ACCOUNT,
107: COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET });
108: new AccountingLineOverride(
109: CODE.EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET,
110: new Integer[] { COMPONENT.EXPIRED_ACCOUNT,
111: COMPONENT.NON_BUDGETED_OBJECT,
112: COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET });
113: new AccountingLineOverride(CODE.NON_FRINGE_ACCOUNT_USED,
114: new Integer[] { COMPONENT.NON_FRINGE_ACCOUNT_USED });
115: new AccountingLineOverride(
116: CODE.EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED,
117: new Integer[] { COMPONENT.EXPIRED_ACCOUNT,
118: COMPONENT.NON_FRINGE_ACCOUNT_USED });
119: }
120:
121: private final String code;
122: private final Set components;
123:
124: /**
125: * This private constructor is for the static initializer.
126: *
127: * @param myCode
128: * @param myComponents
129: */
130: private AccountingLineOverride(String myCode, Integer[] myComponents) {
131: code = myCode;
132: components = componentsAsSet(myComponents);
133: codeToOverrideMap.put(code, this );
134: componentsToOverrideMap.put(components, this );
135: }
136:
137: /**
138: * Checks whether this override contains the given component.
139: *
140: * @param component
141: * @return whether this override contains the given component.
142: */
143: public boolean hasComponent(Integer component) {
144: return components.contains(component);
145: }
146:
147: /**
148: * Gets the code of this override.
149: *
150: * @return the code of this override.
151: */
152: public String getCode() {
153: return code;
154: }
155:
156: /**
157: * Gets the components of this override.
158: *
159: * @return the components of this override.
160: */
161: private Set getComponents() {
162: return components;
163: }
164:
165: /**
166: * @see java.lang.Object#toString()
167: */
168: public String toString() {
169: return "AccountingLineOverride (code " + code + ", components "
170: + components + ")";
171: }
172:
173: /**
174: * Returns the AccountingLineOverride that has the components of this AccountingLineOverride minus any components not in the
175: * given mask. This is like <code>&</code>(a bit-wise and), if the components were bits.
176: *
177: * @param mask
178: * @return the AccountingLineOverride that has the components of this AccountingLineOverride minus any components not in the
179: * given mask.
180: * @throws IllegalArgumentException if there is no such valid combination of components
181: */
182: public AccountingLineOverride mask(AccountingLineOverride mask) {
183: Set key = maskComponents(mask);
184: if (!isValidComponentSet(key)) {
185: throw new IllegalArgumentException("invalid component set "
186: + key);
187: }
188: return valueOf(key);
189: }
190:
191: /**
192: * Returns the Set of components that this override and the given override have in common.
193: *
194: * @param mask
195: * @return the Set of components that this override and the given override have in common.
196: */
197: private Set maskComponents(AccountingLineOverride mask) {
198: Set retval = new HashSet(components);
199: retval.retainAll(mask.getComponents());
200: return retval;
201: }
202:
203: /**
204: * Returns whether this override, when masked by the given override, is valid. Some combinations of components have no override
205: * code defined.
206: *
207: * @param mask
208: * @return whether this override, when masked by the given override, is valid.
209: */
210: public boolean isValidMask(AccountingLineOverride mask) {
211: return isValidComponentSet(maskComponents(mask));
212: }
213:
214: /**
215: * Returns whether the given String is a valid override code.
216: *
217: * @param code
218: * @return whether the given String is a valid override code.
219: */
220: public static boolean isValidCode(String code) {
221: return codeToOverrideMap.containsKey(code);
222: }
223:
224: /**
225: * Returns whether the given Integers are a valid set of components. Some combinations of components are invalid and have no
226: * code defined.
227: *
228: * @param components
229: * @return whether the given Integers are a valid set of components.
230: */
231: public static boolean isValidComponentSet(Integer[] components) {
232: return isValidComponentSet(componentsAsSet(components));
233: }
234:
235: private static boolean isValidComponentSet(Set components) { // todo: JDK 1.5 generic Set
236: return componentsToOverrideMap.containsKey(components);
237: }
238:
239: /**
240: * Factory method from code.
241: *
242: * @param code the override code
243: * @return the AccountingLineOverride instance corresponding to the given code.
244: * @throws IllegalArgumentException if the given code is not valid
245: */
246: public static AccountingLineOverride valueOf(String code) {
247: if (!isValidCode(code)) {
248: throw new IllegalArgumentException("invalid code " + code);
249: }
250: return (AccountingLineOverride) codeToOverrideMap.get(code); // todo: JDK 1.5 generic Map instead of cast
251: }
252:
253: /**
254: * Factory method from components.
255: *
256: * @param components the override components, treated as a set
257: * @return the AccountingLineOverride instance corresponding to the given component set.
258: * @throws IllegalArgumentException if the given set of components is not valid
259: */
260: public static AccountingLineOverride valueOf(Integer[] components) {
261: Set key = componentsAsSet(components);
262: if (!isValidComponentSet(key)) {
263: throw new IllegalArgumentException("invalid component set "
264: + key);
265: }
266: return valueOf(key);
267: }
268:
269: private static AccountingLineOverride valueOf(Set components) {
270: return (AccountingLineOverride) componentsToOverrideMap
271: .get(components); // todo: JDK 1.5 generic Map instead of cast
272: }
273:
274: private static Set componentsAsSet(Integer[] components) {
275: return Collections.unmodifiableSet(new HashSet(Arrays
276: .asList(components)));
277: }
278:
279: /**
280: * On the given AccountingLine, converts override input checkboxes from a Struts Form into a persistable override code.
281: *
282: * @param line
283: */
284: public static void populateFromInput(AccountingLine line) {
285: // todo: this logic won't work if a single account checkbox might also stands for NON_FRINGE_ACCOUNT_USED; needs thought
286:
287: Set overrideInputComponents = new HashSet();
288: if (line.getAccountExpiredOverride()) {
289: overrideInputComponents.add(COMPONENT.EXPIRED_ACCOUNT);
290: }
291: if (line.isObjectBudgetOverride()) {
292: overrideInputComponents.add(COMPONENT.NON_BUDGETED_OBJECT);
293: }
294: if (!isValidComponentSet(overrideInputComponents)) {
295: // todo: error for invalid override checkbox combinations, for which there is no override code
296: }
297: line
298: .setOverrideCode(valueOf(overrideInputComponents)
299: .getCode());
300: }
301:
302: /**
303: * Prepares the given AccountingLine in a Struts Action for display by a JSP. This means converting the override code to
304: * checkboxes for display and input, as well as analysing the accounting line and determining which override checkboxes are
305: * needed.
306: *
307: * @param line
308: */
309: public static void processForOutput(AccountingLine line) {
310: AccountingLineOverride fromCurrentCode = valueOf(line
311: .getOverrideCode());
312: AccountingLineOverride needed = determineNeededOverrides(line);
313: line.setAccountExpiredOverride(fromCurrentCode
314: .hasComponent(COMPONENT.EXPIRED_ACCOUNT));
315: line.setAccountExpiredOverrideNeeded(needed
316: .hasComponent(COMPONENT.EXPIRED_ACCOUNT));
317: line.setObjectBudgetOverride(fromCurrentCode
318: .hasComponent(COMPONENT.NON_BUDGETED_OBJECT));
319: line.setObjectBudgetOverrideNeeded(needed
320: .hasComponent(COMPONENT.NON_BUDGETED_OBJECT));
321: }
322:
323: /**
324: * Determines what overrides the given line needs.
325: *
326: * @param line
327: * @return what overrides the given line needs.
328: */
329: public static AccountingLineOverride determineNeededOverrides(
330: AccountingLine line) {
331: Set neededOverrideComponents = new HashSet();
332: if (needsExpiredAccountOverride(line.getAccount())) {
333: neededOverrideComponents.add(COMPONENT.EXPIRED_ACCOUNT);
334: }
335: if (needsObjectBudgetOverride(line.getAccount(), line
336: .getObjectCode())) {
337: neededOverrideComponents.add(COMPONENT.NON_BUDGETED_OBJECT);
338: }
339:
340: if (!isValidComponentSet(neededOverrideComponents)) {
341: // todo: error for invalid override checkbox combinations, for which there is no override code
342: }
343: return valueOf(neededOverrideComponents);
344: }
345:
346: /**
347: * Returns whether the given account needs an expired account override.
348: *
349: * @param account
350: * @return whether the given account needs an expired account override.
351: */
352: public static boolean needsExpiredAccountOverride(Account account) {
353: return !ObjectUtils.isNull(account)
354: && !account.isAccountClosedIndicator()
355: && account.isExpired();
356: }
357:
358: /**
359: * Returns whether the given account needs an expired account override.
360: *
361: * @param account
362: * @return whether the given account needs an expired account override.
363: */
364: public static boolean needsNonFringAccountOverride(Account account) {
365: return !ObjectUtils.isNull(account)
366: && !account.isAccountClosedIndicator()
367: && !account.isAccountsFringesBnftIndicator();
368: }
369:
370: /**
371: * Returns whether the given object code needs an object budget override
372: *
373: * @param account
374: * @return whether the given object code needs an object budget override
375: */
376: public static boolean needsObjectBudgetOverride(Account account,
377: ObjectCode objectCode) {
378: return !ObjectUtils.isNull(account)
379: && !ObjectUtils.isNull(objectCode)
380: && !account.isAccountClosedIndicator()
381: && !SpringContext.getBean(AccountPresenceService.class)
382: .isObjectCodeBudgetedForAccountPresence(
383: account, objectCode);
384: }
385: }
|