001: /*
002: *
003: * JMoney - A Personal Finance Manager
004: * Copyright (c) 2002 Johann Gyger <johann.gyger@switzerland.org>
005: * Copyright (c) 2004 Nigel Westbury <westbury@users.sourceforge.net>
006: *
007: *
008: * This program is free software; you can redistribute it and/or modify
009: * it under the terms of the GNU General Public License as published by
010: * the Free Software Foundation; either version 2 of the License, or
011: * (at your option) any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
021: *
022: */
023:
024: package net.sf.jmoney.model2;
025:
026: import java.security.InvalidParameterException;
027: import java.util.Collection;
028: import java.util.Hashtable;
029: import java.util.Iterator;
030: import java.util.Vector;
031:
032: import net.sf.jmoney.JMoneyPlugin;
033: import net.sf.jmoney.isolation.TransactionManager;
034:
035: import org.eclipse.core.commands.operations.IUndoContext;
036: import org.eclipse.core.commands.operations.UndoContext;
037: import org.eclipse.core.runtime.IAdaptable;
038:
039: /**
040: * Holds the fields that will be saved in a file.
041: */
042: public class Session extends ExtendableObject implements IAdaptable {
043:
044: IObjectKey defaultCurrencyKey;
045:
046: private IListManager<Commodity> commodities;
047:
048: private IListManager<Account> accounts; // Only the Accounts of Level 0
049:
050: private IListManager<Transaction> transactions;
051:
052: /**
053: * A map cache of currencies, used to get a currency object given
054: * a currency code. This cache must be updated as currencies are
055: * added, removed, or when a currency's code changes.
056: */
057: Hashtable<String, Currency> currencies = new Hashtable<String, Currency>();
058:
059: private ChangeManager changeManager = new ChangeManager();
060:
061: private IUndoContext undoContext = new UndoContext();
062:
063: /**
064: * Constructor used by datastore plug-ins to create
065: * a session object.
066: */
067: public Session(IObjectKey objectKey, ListKey parentKey,
068: IListManager<Commodity> commodities,
069: IListManager<Account> accounts,
070: IListManager<Transaction> transactions,
071: IObjectKey defaultCurrencyKey, IValues extensionValues) {
072: super (objectKey, parentKey, extensionValues);
073:
074: this .commodities = commodities;
075: this .accounts = accounts;
076: this .transactions = transactions;
077: this .defaultCurrencyKey = defaultCurrencyKey;
078:
079: /*
080: * Load the currencies into our cached map.
081: */
082: for (Commodity commodity : commodities) {
083: if (commodity instanceof Currency) {
084: Currency currency = (Currency) commodity;
085: if (currency.getCode() != null) {
086: this .currencies.put(currency.getCode(), currency);
087: }
088: }
089: }
090: }
091:
092: /**
093: * Constructor used by datastore plug-ins to create
094: * a session object.
095: */
096: public Session(IObjectKey objectKey, ListKey parentKey) {
097: super (objectKey, parentKey);
098:
099: this .commodities = objectKey.constructListManager(SessionInfo
100: .getCommoditiesAccessor());
101: this .accounts = objectKey.constructListManager(SessionInfo
102: .getAccountsAccessor());
103: this .transactions = objectKey.constructListManager(SessionInfo
104: .getTransactionsAccessor());
105: this .defaultCurrencyKey = null;
106: }
107:
108: @Override
109: protected String getExtendablePropertySetId() {
110: return "net.sf.jmoney.session";
111: }
112:
113: public Currency getDefaultCurrency() {
114: return defaultCurrencyKey == null ? null
115: : (Currency) defaultCurrencyKey.getObject();
116: }
117:
118: /**
119: *
120: * @param defaultCurrency the default currency, which cannot
121: * be null because a default currency must always
122: * be set for a session
123: */
124: public void setDefaultCurrency(Currency defaultCurrency) {
125: Currency oldDefaultCurrency = getDefaultCurrency();
126: this .defaultCurrencyKey = defaultCurrency.getObjectKey();
127:
128: // Notify the change manager.
129: processPropertyChange(SessionInfo.getDefaultCurrencyAccessor(),
130: oldDefaultCurrency, defaultCurrency);
131: }
132:
133: /**
134: * @param code the currency code.
135: * @return the corresponding currency.
136: */
137: public Currency getCurrencyForCode(String code) {
138: return currencies.get(code);
139: }
140:
141: public Collection<Account> getAllAccounts() {
142: Vector<Account> all = new Vector<Account>();
143: for (Account a : getAccountCollection()) {
144: all.add(a);
145: all.addAll(a.getAllSubAccounts());
146: }
147: return all;
148: }
149:
150: public Iterator<CapitalAccount> getCapitalAccountIterator() {
151: return new Iterator<CapitalAccount>() {
152: Iterator<Account> iter = accounts.iterator();
153: CapitalAccount element;
154:
155: public boolean hasNext() {
156: while (iter.hasNext()) {
157: Account account = iter.next();
158: if (account instanceof CapitalAccount) {
159: element = (CapitalAccount) account;
160: return true;
161: }
162: }
163: return false;
164: }
165:
166: public CapitalAccount next() {
167: return element;
168: }
169:
170: public void remove() {
171: throw new UnsupportedOperationException();
172: }
173: };
174: }
175:
176: public Iterator<IncomeExpenseAccount> getIncomeExpenseAccountIterator() {
177: return new Iterator<IncomeExpenseAccount>() {
178: Iterator<Account> iter = accounts.iterator();
179: IncomeExpenseAccount element;
180:
181: public boolean hasNext() {
182: while (iter.hasNext()) {
183: Account account = iter.next();
184: if (account instanceof IncomeExpenseAccount) {
185: element = (IncomeExpenseAccount) account;
186: return true;
187: }
188: }
189: return false;
190: }
191:
192: public IncomeExpenseAccount next() {
193: return element;
194: }
195:
196: public void remove() {
197: throw new UnsupportedOperationException();
198: }
199: };
200: }
201:
202: public ObjectCollection<Commodity> getCommodityCollection() {
203: return new ObjectCollection<Commodity>(commodities, this ,
204: SessionInfo.getCommoditiesAccessor());
205: }
206:
207: public ObjectCollection<Account> getAccountCollection() {
208: return new ObjectCollection<Account>(accounts, this ,
209: SessionInfo.getAccountsAccessor());
210: }
211:
212: public ObjectCollection<Transaction> getTransactionCollection() {
213: return new ObjectCollection<Transaction>(transactions, this ,
214: SessionInfo.getTransactionsAccessor());
215: }
216:
217: /**
218: * Create a new account. Accounts are abstract, so
219: * a property set derived from the account property
220: * set must be passed to this method. When an account
221: * is created, various objects, such as the objects that manage collections within
222: * the account, must be created. The implementation of
223: * these objects depends on the datastore and must be passed
224: * to the constructor, so the actual construction of the object
225: * is delegated to the collection object that will hold this
226: * new account. (The implementation of the collection object
227: * is provided by the datastore plug-in, so this object knows
228: * how to create an object in a way that is appropriate for
229: * the datastore).
230: *
231: * The collection object will get the properties for the new
232: * object from the given interface. Scalar properties are
233: * simply set. References to other objects are likewise set.
234: * This means any referenced object must have been fetched by
235: * the datastore.
236: *
237: * @param accountPropertySet
238: * @param account
239: * @return
240: */
241: public <A extends Account> A createAccount(
242: ExtendablePropertySet<A> propertySet) {
243: return getAccountCollection().createNewElement(propertySet);
244: }
245:
246: /**
247: * Create a new commodity. Commodities are abstract, so
248: * a property set derived from the commodity property
249: * set must be passed to this method. When an commodity
250: * is created, various objects, such as the objects that manage collections within
251: * the commodity, must be created. The implementation of
252: * these objects depends on the datastore and must be passed
253: * to the constructor, so the actual construction of the object
254: * is delegated to the collection object that will hold this
255: * new commodity. (The implementation of the collection object
256: * is provided by the datastore plug-in, so this object knows
257: * how to create an object in a way that is appropriate for
258: * the datastore).
259: *
260: * The collection object will get the properties for the new
261: * object from the given interface. Scalar properties are
262: * simply set. References to other objects are likewise set.
263: * This means any referenced object must have been fetched by
264: * the datastore.
265: *
266: * @param commodityPropertySet
267: * @param commodity
268: * @return
269: */
270: public <E extends Commodity> E createCommodity(
271: ExtendablePropertySet<E> propertySet) {
272: return getCommodityCollection().createNewElement(propertySet);
273: }
274:
275: public Transaction createTransaction() {
276: return getTransactionCollection().createNewElement(
277: TransactionInfo.getPropertySet());
278: }
279:
280: public boolean deleteCommodity(Commodity commodity) {
281: return getCommodityCollection().remove(commodity);
282: }
283:
284: /**
285: * Removes the specified account from this collection,
286: * if it is present.
287: * <P>
288: * Note that accounts may be sub-accounts. Only top
289: * level accounts are in this session's account list.
290: * Sub-accounts must be removed by calling the
291: * <code>removeSubAccount</code> method on the parent account.
292: *
293: * @param account Account to be removed from this collection.
294: * This parameter may not be null.
295: * @return true if the account was present, false if the account
296: * was not present in the collection.
297: */
298: public boolean deleteAccount(Account account) {
299: Account parent = account.getParent();
300: if (parent == null) {
301: return getAccountCollection().remove(account);
302: } else {
303: // Pass the request on to the parent account.
304: return parent.deleteSubAccount(account);
305: }
306: }
307:
308: public boolean deleteTransaction(Transaction transaction) {
309: return getTransactionCollection().remove(transaction);
310: }
311:
312: public ChangeManager getChangeManager() {
313: return changeManager;
314: }
315:
316: /**
317: * @author Faucheux
318: * TODO: Faucheux - not the better algorythm!
319: */
320: public Account getAccountByFullName(String name) {
321: for (Account a : getAllAccounts()) {
322: if (a.getFullAccountName().equals(name))
323: return a;
324: }
325: return null;
326: }
327:
328: /**
329: * @throws InvalidParameterException
330: * @author Faucheux
331: * TODO: Faucheux - not the better algorythm!
332: */
333: public Account getAccountByShortName(String name)
334: throws SeveralAccountsFoundException,
335: NoAccountFoundException {
336: Account foundAccount = null;
337: Iterator it = getAllAccounts().iterator();
338: while (it.hasNext()) {
339: Account a = (Account) it.next();
340: if (a.getName().equals(name)) {
341: if (foundAccount != null) {
342: throw new SeveralAccountsFoundException();
343: } else {
344: foundAccount = a;
345: }
346: }
347: }
348: if (foundAccount == null)
349: throw new NoAccountFoundException();
350: return foundAccount;
351: }
352:
353: /**
354: * Certain operations may be executed more efficiently by the datastore.
355: * For example, if the datastore is implemented on top of a database and
356: * we need to get an account balance, it is far more efficient to submit
357: * a statement of the form "select sum(amount) from entries where account = ?"
358: * to the database than to iterate through the entries, a process that
359: * requires constructing each entry from data in the database.
360: * <P>
361: * Datastores may optionally implement any adapter interface.
362: * A datastore does not have to implement an adapter interface. Therefore
363: * all consumers must provide an alternative algorithm for obtaining the
364: * same results.
365: * <P>
366: * These interfaces are obtained as adapters for two reasons:
367: * <OL>
368: * <LI>The implementation is optional
369: * </LI>
370: * <LI>More importantly, new types of queries may be added by plug-ins.
371: * The datastore plug-ins may not even know about such queries.
372: * </LI>
373: * </OL>
374: */
375: public Object getAdapter(Class adapter) {
376: // Pass the request on to the session manager.
377: // The SessionManager object is implemented by the datastore plug-in
378: // and therefore can provide a set of interface implementations that
379: // are optimized for the datastore.
380: return getDataManager().getAdapter(adapter);
381: }
382:
383: public class NoAccountFoundException extends Exception {
384: private static final long serialVersionUID = -6022196945540827504L;
385: }
386:
387: public class SeveralAccountsFoundException extends Exception {
388: private static final long serialVersionUID = -6427097946645258873L;
389: }
390:
391: /**
392: * JMoney supports undo/redo using a context that is based on the data
393: * manager.
394: *
395: * The underlying data manager (supported by the underlying datastore) and
396: * the transaction manager session objects each have a different context.
397: *
398: * Changes within a transaction manager have a separate context. This is
399: * necessary because if a view is making changes to an through a transaction
400: * manager to an uncommitted version of the datastore then the undo/redo
401: * menu needs to show those changes for the view.
402: *
403: * @return the undo/redo context to be used for changes made to this session
404: */
405: public IUndoContext getUndoContext() {
406: // If not in a transaction, use the workbench context.
407: // This may need some tidying up, but by using a common context,
408: // this allows undo/redo to work even across a closing or
409: // opening of a session. There may be a better way of doing this.
410: if (this .getDataManager() instanceof TransactionManager) {
411: return undoContext;
412: } else {
413: return JMoneyPlugin.getDefault().getWorkbench()
414: .getOperationSupport().getUndoContext();
415: }
416: }
417: }
|