1 | |
|
2 | |
|
3 | |
|
4 | |
package org.homeunix.thecave.buddi.model.impl; |
5 | |
|
6 | |
import java.beans.XMLDecoder; |
7 | |
import java.io.File; |
8 | |
import java.io.FileInputStream; |
9 | |
import java.io.IOException; |
10 | |
import java.io.InputStream; |
11 | |
import java.util.Collections; |
12 | |
import java.util.Date; |
13 | |
import java.util.HashMap; |
14 | |
import java.util.Map; |
15 | |
import java.util.logging.Logger; |
16 | |
|
17 | |
import javax.swing.JOptionPane; |
18 | |
|
19 | |
import org.homeunix.thecave.buddi.Const; |
20 | |
import org.homeunix.thecave.buddi.i18n.BuddiKeys; |
21 | |
import org.homeunix.thecave.buddi.i18n.keys.BudgetCategoryTypes; |
22 | |
import org.homeunix.thecave.buddi.i18n.keys.BudgetExpenseDefaultKeys; |
23 | |
import org.homeunix.thecave.buddi.i18n.keys.BudgetIncomeDefaultKeys; |
24 | |
import org.homeunix.thecave.buddi.i18n.keys.ButtonKeys; |
25 | |
import org.homeunix.thecave.buddi.i18n.keys.TypeCreditDefaultKeys; |
26 | |
import org.homeunix.thecave.buddi.i18n.keys.TypeDebitDefaultKeys; |
27 | |
import org.homeunix.thecave.buddi.model.Account; |
28 | |
import org.homeunix.thecave.buddi.model.AccountType; |
29 | |
import org.homeunix.thecave.buddi.model.BudgetCategory; |
30 | |
import org.homeunix.thecave.buddi.model.BudgetCategoryType; |
31 | |
import org.homeunix.thecave.buddi.model.Document; |
32 | |
import org.homeunix.thecave.buddi.model.ScheduledTransaction; |
33 | |
import org.homeunix.thecave.buddi.model.Source; |
34 | |
import org.homeunix.thecave.buddi.model.Split; |
35 | |
import org.homeunix.thecave.buddi.model.Transaction; |
36 | |
import org.homeunix.thecave.buddi.model.TransactionSplit; |
37 | |
import org.homeunix.thecave.buddi.plugin.api.exception.InvalidValueException; |
38 | |
import org.homeunix.thecave.buddi.plugin.api.exception.ModelException; |
39 | |
import org.homeunix.thecave.buddi.plugin.api.util.TextFormatter; |
40 | |
import org.homeunix.thecave.buddi.util.BuddiCryptoFactory; |
41 | |
import org.homeunix.thecave.buddi.util.OperationCancelledException; |
42 | |
import org.homeunix.thecave.buddi.view.dialogs.BuddiPasswordDialog; |
43 | |
|
44 | |
import ca.digitalcave.moss.application.document.exception.DocumentLoadException; |
45 | |
import ca.digitalcave.moss.common.DateUtil; |
46 | |
import ca.digitalcave.moss.common.OperatingSystemUtil; |
47 | |
import ca.digitalcave.moss.crypto.CipherException; |
48 | |
import ca.digitalcave.moss.crypto.IncorrectDocumentFormatException; |
49 | |
import ca.digitalcave.moss.crypto.IncorrectPasswordException; |
50 | |
|
51 | |
|
52 | |
|
53 | |
|
54 | |
|
55 | |
|
56 | |
|
57 | |
|
58 | 0 | public class ModelFactory { |
59 | |
|
60 | |
public static Map<String, BudgetCategoryType> budgetPeriodTypes; |
61 | |
|
62 | |
|
63 | |
|
64 | |
|
65 | |
|
66 | |
|
67 | |
|
68 | |
public static BudgetCategoryType getBudgetCategoryType(BudgetCategoryTypes type){ |
69 | 24263 | return getBudgetCategoryType(type.toString()); |
70 | |
} |
71 | |
|
72 | |
|
73 | |
|
74 | |
|
75 | |
|
76 | |
|
77 | |
|
78 | |
public static BudgetCategoryType getBudgetCategoryType(String name){ |
79 | 24263 | if (budgetPeriodTypes == null){ |
80 | 2986 | budgetPeriodTypes = new HashMap<String, BudgetCategoryType>(); |
81 | 2986 | budgetPeriodTypes.put(BudgetCategoryTypes.BUDGET_CATEGORY_TYPE_MONTH.toString(), new BudgetCategoryTypeMonthly()); |
82 | 2986 | budgetPeriodTypes.put(BudgetCategoryTypes.BUDGET_CATEGORY_TYPE_WEEK.toString(), new BudgetCategoryTypeWeekly()); |
83 | 2986 | budgetPeriodTypes.put(BudgetCategoryTypes.BUDGET_CATEGORY_TYPE_SEMI_MONTH.toString(), new BudgetCategoryTypeSemiMonthly()); |
84 | 2986 | budgetPeriodTypes.put(BudgetCategoryTypes.BUDGET_CATEGORY_TYPE_QUARTER.toString(), new BudgetCategoryTypeQuarterly()); |
85 | 2986 | budgetPeriodTypes.put(BudgetCategoryTypes.BUDGET_CATEGORY_TYPE_SEMI_YEAR.toString(), new BudgetCategoryTypeSemiYearly()); |
86 | 2986 | budgetPeriodTypes.put(BudgetCategoryTypes.BUDGET_CATEGORY_TYPE_YEAR.toString(), new BudgetCategoryTypeYearly()); |
87 | |
} |
88 | |
|
89 | 24263 | return budgetPeriodTypes.get(name); |
90 | |
} |
91 | |
|
92 | |
|
93 | |
|
94 | |
|
95 | |
|
96 | |
|
97 | |
|
98 | |
|
99 | |
public static Account createAccount(String name, AccountType type) throws InvalidValueException { |
100 | 0 | Account a = new AccountImpl(); |
101 | |
|
102 | 0 | a.setName(name); |
103 | 0 | a.setAccountType(type); |
104 | |
|
105 | 0 | return a; |
106 | |
} |
107 | |
|
108 | 2986 | private static Split split = new SplitImpl(); |
109 | |
public static Split createSplit() throws InvalidValueException { |
110 | 0 | return split; |
111 | |
} |
112 | |
|
113 | |
public static TransactionSplit createTransactionSplit(Source source, long amount) throws InvalidValueException { |
114 | 103 | TransactionSplit t = new TransactionSplitImpl(); |
115 | |
|
116 | 103 | t.setSource(source); |
117 | 103 | t.setAmount(amount); |
118 | |
|
119 | 103 | return t; |
120 | |
} |
121 | |
|
122 | |
|
123 | |
|
124 | |
|
125 | |
|
126 | |
|
127 | |
|
128 | |
|
129 | |
public static AccountType createAccountType(String name, boolean credit) throws InvalidValueException { |
130 | 27306 | AccountType at = new AccountTypeImpl(); |
131 | |
|
132 | 27306 | at.setName(name); |
133 | 27306 | at.setCredit(credit); |
134 | |
|
135 | 27306 | return at; |
136 | |
} |
137 | |
|
138 | |
|
139 | |
|
140 | |
|
141 | |
|
142 | |
|
143 | |
|
144 | |
|
145 | |
|
146 | |
public static BudgetCategory createBudgetCategory(String name, BudgetCategoryType type, boolean income) throws InvalidValueException { |
147 | 30340 | BudgetCategory bc = new BudgetCategoryImpl(); |
148 | |
|
149 | 30340 | bc.setName(name); |
150 | 30340 | bc.setPeriodType(type); |
151 | 30340 | bc.setIncome(income); |
152 | |
|
153 | 30340 | return bc; |
154 | |
} |
155 | |
|
156 | |
|
157 | |
|
158 | |
|
159 | |
public static Document createDocument() throws ModelException { |
160 | |
Document document; |
161 | 3034 | if (getAutoSaveLocation(null).exists() && getAutoSaveLocation(null).canRead()){ |
162 | 0 | Logger.getLogger(ModelFactory.class.getName()).info("Autosave file found; prompting user if we should use it or not"); |
163 | |
|
164 | 0 | String[] options = new String[2]; |
165 | 0 | options[0] = TextFormatter.getTranslation(ButtonKeys.BUTTON_YES); |
166 | 0 | options[1] = TextFormatter.getTranslation(ButtonKeys.BUTTON_NO); |
167 | |
|
168 | 0 | if (JOptionPane.showOptionDialog( |
169 | |
null, |
170 | |
TextFormatter.getTranslation(BuddiKeys.MESSAGE_AUTOSAVE_FILE_FOUND), |
171 | |
TextFormatter.getTranslation(BuddiKeys.MESSAGE_AUTOSAVE_FILE_FOUND_TITLE), |
172 | |
JOptionPane.YES_NO_OPTION, |
173 | |
JOptionPane.QUESTION_MESSAGE, |
174 | |
null, |
175 | |
options, |
176 | |
options[0] |
177 | |
) == 0) { |
178 | |
try { |
179 | 0 | document = createDocument(getAutoSaveLocation(null)); |
180 | 0 | document.setFile(null); |
181 | 0 | document.setChanged(); |
182 | 0 | Logger.getLogger(ModelFactory.class.getName()).info("User decided to load AutoSave file"); |
183 | 0 | return document; |
184 | |
} |
185 | 0 | catch (DocumentLoadException dle){ |
186 | 0 | Logger.getLogger(ModelFactory.class.getName()).warning("Error opening auto save file. Continuing on to create normal file."); |
187 | |
} |
188 | 0 | catch (OperationCancelledException oce){ |
189 | 0 | Logger.getLogger(ModelFactory.class.getName()).finest("User cancelled opening auto save file. Continuing on to create normal file."); |
190 | 0 | } |
191 | |
} |
192 | |
else { |
193 | 0 | Logger.getLogger(ModelFactory.class.getName()).info("User decided not to load AutoSave file. It will be removed the next time this file is saved."); |
194 | |
} |
195 | |
} |
196 | |
|
197 | 3034 | document = new DocumentImpl(); |
198 | 3034 | document.setFile(null); |
199 | |
|
200 | 24272 | for (BudgetExpenseDefaultKeys s : BudgetExpenseDefaultKeys.values()){ |
201 | 21238 | BudgetCategory bc = ModelFactory.createBudgetCategory(s.toString(), new BudgetCategoryTypeMonthly(), false); |
202 | 21238 | document.addBudgetCategory(bc); |
203 | |
} |
204 | 12136 | for (BudgetIncomeDefaultKeys s : BudgetIncomeDefaultKeys.values()){ |
205 | 9102 | document.addBudgetCategory(ModelFactory.createBudgetCategory(s.toString(), new BudgetCategoryTypeMonthly(), true)); |
206 | |
} |
207 | |
|
208 | 15170 | for (TypeDebitDefaultKeys s : TypeDebitDefaultKeys.values()){ |
209 | 12136 | document.addAccountType(ModelFactory.createAccountType(s.toString(), false)); |
210 | |
} |
211 | |
|
212 | 18204 | for (TypeCreditDefaultKeys s : TypeCreditDefaultKeys.values()){ |
213 | 15170 | document.addAccountType(ModelFactory.createAccountType(s.toString(), true)); |
214 | |
} |
215 | |
|
216 | |
|
217 | 3034 | document.refreshUidMap(); |
218 | |
|
219 | |
|
220 | 3034 | String errors = document.doSanityChecks(); |
221 | 3034 | if (errors != null) |
222 | 0 | JOptionPane.showMessageDialog(null, errors); |
223 | |
|
224 | |
|
225 | 3034 | document.updateAllBalances(); |
226 | |
|
227 | |
|
228 | 3034 | Collections.sort(document.getAccounts()); |
229 | 3034 | Collections.sort(document.getAccountTypes()); |
230 | 3034 | Collections.sort(document.getBudgetCategories()); |
231 | 3034 | Collections.sort(document.getTransactions()); |
232 | 3034 | Collections.sort(document.getScheduledTransactions()); |
233 | |
|
234 | |
|
235 | 3034 | document.finishBatchChange(); |
236 | |
|
237 | |
|
238 | 3034 | document.setChanged(); |
239 | |
|
240 | |
|
241 | 3034 | return document; |
242 | |
} |
243 | |
|
244 | |
|
245 | |
|
246 | |
|
247 | |
|
248 | |
|
249 | |
|
250 | |
|
251 | |
public static Document createDocument(File file) throws DocumentLoadException, OperationCancelledException { |
252 | |
DocumentImpl document; |
253 | |
|
254 | |
|
255 | |
|
256 | |
|
257 | 0 | File fileToLoad = file; |
258 | |
|
259 | 0 | if (file == null) |
260 | 0 | throw new DocumentLoadException("Error loading model: specfied file is null."); |
261 | |
|
262 | 0 | if (!file.exists()) |
263 | 0 | throw new DocumentLoadException("File " + file + " does not exist."); |
264 | |
|
265 | 0 | if (!file.canRead()) |
266 | 0 | throw new DocumentLoadException("File " + file + " cannot be opened for reading."); |
267 | |
|
268 | 0 | if (getAutoSaveLocation(file).exists() && getAutoSaveLocation(file).canRead()){ |
269 | 0 | Logger.getLogger(ModelFactory.class.getName()).info("Autosave file found; prompting user if we should use it or not"); |
270 | |
|
271 | 0 | String[] options = new String[2]; |
272 | 0 | options[0] = TextFormatter.getTranslation(ButtonKeys.BUTTON_YES); |
273 | 0 | options[1] = TextFormatter.getTranslation(ButtonKeys.BUTTON_NO); |
274 | |
|
275 | 0 | if (JOptionPane.showOptionDialog( |
276 | |
null, |
277 | |
TextFormatter.getTranslation(BuddiKeys.MESSAGE_AUTOSAVE_FILE_FOUND), |
278 | |
TextFormatter.getTranslation(BuddiKeys.MESSAGE_AUTOSAVE_FILE_FOUND_TITLE), |
279 | |
JOptionPane.YES_NO_OPTION, |
280 | |
JOptionPane.QUESTION_MESSAGE, |
281 | |
null, |
282 | |
options, |
283 | |
options[0] |
284 | |
) == 0) { |
285 | 0 | fileToLoad = getAutoSaveLocation(file); |
286 | 0 | Logger.getLogger(ModelFactory.class.getName()).info("User decided to load AutoSave file"); |
287 | |
} |
288 | |
else { |
289 | 0 | Logger.getLogger(ModelFactory.class.getName()).info("User decided not to load AutoSave file. It will be removed the next time this file is saved."); |
290 | |
} |
291 | |
} |
292 | |
|
293 | 0 | Logger.getLogger(ModelFactory.class.getName()).finest("Trying to load file " + fileToLoad); |
294 | |
|
295 | |
try { |
296 | |
InputStream is; |
297 | 0 | BuddiCryptoFactory factory = new BuddiCryptoFactory(); |
298 | 0 | char[] password = null; |
299 | |
|
300 | |
|
301 | |
|
302 | |
while (true) { |
303 | |
try { |
304 | 0 | is = factory.getDecryptedStream(new FileInputStream(fileToLoad), password); |
305 | |
|
306 | |
|
307 | 0 | XMLDecoder decoder = new XMLDecoder(is); |
308 | 0 | Object o = decoder.readObject(); |
309 | 0 | if (o instanceof DocumentImpl){ |
310 | 0 | document = (DocumentImpl) o; |
311 | |
} |
312 | |
else { |
313 | 0 | throw new IncorrectDocumentFormatException("Could not find a DataModelBean object in the data file!"); |
314 | |
} |
315 | 0 | is.close(); |
316 | |
|
317 | |
|
318 | 0 | document.refreshUidMap(); |
319 | |
|
320 | |
|
321 | |
|
322 | |
|
323 | 0 | document.setFile(file); |
324 | |
|
325 | |
|
326 | 0 | document.updateAllBalances(); |
327 | |
|
328 | |
|
329 | 0 | document.setPassword(password); |
330 | |
|
331 | |
|
332 | 0 | document.updateScheduledTransactions(); |
333 | |
|
334 | |
|
335 | |
|
336 | |
|
337 | |
|
338 | |
|
339 | |
|
340 | |
|
341 | |
|
342 | |
|
343 | |
|
344 | |
|
345 | |
|
346 | 0 | for (BudgetCategory bc : document.getBudgetCategories()) { |
347 | 0 | boolean changed = false; |
348 | 0 | Map<String, Long> newAmountMap = new HashMap<String, Long>(); |
349 | |
|
350 | |
|
351 | |
|
352 | 0 | for (String key : ((BudgetCategoryImpl) bc).getAmounts().keySet()) { |
353 | 0 | String[] splitKey = key.split(":"); |
354 | 0 | if (splitKey.length == 2){ |
355 | 0 | changed = true; |
356 | 0 | Date dateKey = new Date(Long.parseLong(splitKey[1])); |
357 | 0 | Logger.getLogger(ModelFactory.class.getName()).finest("dateKey: " + dateKey); |
358 | 0 | long amount = ((BudgetCategoryImpl) bc).getAmounts().get(key); |
359 | 0 | String newDateKey = |
360 | |
bc.getBudgetPeriodType().getName() |
361 | |
+ ":" + DateUtil.getYear(dateKey) |
362 | |
+ ":" + DateUtil.getMonth(dateKey) |
363 | |
+ ":" + DateUtil.getDay(dateKey); |
364 | 0 | newAmountMap.put(newDateKey, amount); |
365 | 0 | } |
366 | 0 | else if (splitKey.length == 4){ |
367 | 0 | newAmountMap.put(key, ((BudgetCategoryImpl) bc).getAmounts().get(key)); |
368 | |
} |
369 | 0 | } |
370 | |
|
371 | 0 | if (changed){ |
372 | 0 | Logger.getLogger(ModelFactory.class.getName()).warning("Your data file has been updated to address bug #1811038. Included inline are the details of the changes:\n\n" |
373 | |
+ "New Map:\n" + newAmountMap + "\n\n" |
374 | |
+ "Old Map:\n" + ((BudgetCategoryImpl) bc).getAmounts()); |
375 | |
|
376 | 0 | ((BudgetCategoryImpl) bc).setAmounts(newAmountMap); |
377 | |
} |
378 | 0 | } |
379 | |
|
380 | |
|
381 | |
|
382 | |
|
383 | |
|
384 | |
|
385 | 0 | for (Transaction t : document.getTransactions()) { |
386 | 0 | t.getDate(); |
387 | |
} |
388 | |
|
389 | |
|
390 | |
|
391 | |
|
392 | 0 | Collections.sort(document.getAccounts()); |
393 | 0 | Collections.sort(document.getAccountTypes()); |
394 | 0 | Collections.sort(document.getBudgetCategories()); |
395 | 0 | Collections.sort(document.getTransactions()); |
396 | 0 | Collections.sort(document.getScheduledTransactions()); |
397 | |
|
398 | |
|
399 | 0 | document.finishBatchChange(); |
400 | |
|
401 | |
|
402 | 0 | document.setChanged(); |
403 | 0 | document.resetChanged(); |
404 | |
|
405 | |
|
406 | 0 | return document; |
407 | |
} |
408 | 0 | catch (IncorrectPasswordException ipe){ |
409 | |
|
410 | 0 | BuddiPasswordDialog passwordDialog = new BuddiPasswordDialog(); |
411 | 0 | password = passwordDialog.askForPassword(false, true); |
412 | |
|
413 | |
|
414 | |
|
415 | 0 | if (password == null) |
416 | 0 | throw new OperationCancelledException("User cancelled file load."); |
417 | |
} |
418 | 0 | catch (IncorrectDocumentFormatException ife){ |
419 | |
|
420 | |
|
421 | 0 | throw new DocumentLoadException("Incorrect document format", ife); |
422 | |
} |
423 | 0 | catch (ModelException me){ |
424 | 0 | throw new DocumentLoadException("Model exception", me); |
425 | 0 | } |
426 | |
} |
427 | |
} |
428 | 0 | catch (CipherException ce){ |
429 | |
|
430 | |
|
431 | 0 | throw new DocumentLoadException(ce); |
432 | |
} |
433 | 0 | catch (IOException ioe){ |
434 | |
|
435 | 0 | throw new DocumentLoadException(ioe); |
436 | |
} |
437 | |
} |
438 | |
|
439 | |
|
440 | |
|
441 | |
|
442 | |
|
443 | |
|
444 | |
|
445 | |
|
446 | |
public static ScheduledTransaction createScheduledTransaction(String name, String message, Date startDate, Date endDate, String frequencyType, int scheduleDay, int scheduleWeek, int scheduleMonth, String description, long amount, Source from, Source to) throws InvalidValueException { |
447 | |
|
448 | 0 | ScheduledTransaction st = new ScheduledTransactionImpl(); |
449 | |
|
450 | 0 | st.setScheduleName(name); |
451 | 0 | st.setMessage(message); |
452 | 0 | st.setStartDate(startDate); |
453 | 0 | if (endDate != null) |
454 | 0 | st.setEndDate(endDate); |
455 | 0 | st.setFrequencyType(frequencyType); |
456 | 0 | st.setScheduleDay(scheduleDay); |
457 | 0 | st.setScheduleWeek(scheduleWeek); |
458 | 0 | st.setScheduleMonth(scheduleMonth); |
459 | |
|
460 | 0 | st.setDescription(description); |
461 | 0 | st.setAmount(amount); |
462 | 0 | st.setFrom(from); |
463 | 0 | st.setTo(to); |
464 | |
|
465 | 0 | return st; |
466 | |
} |
467 | |
|
468 | |
|
469 | |
|
470 | |
|
471 | |
|
472 | |
|
473 | |
|
474 | |
|
475 | |
|
476 | |
|
477 | |
|
478 | |
public static Transaction createTransaction(Date date, String description, long amount, Source from, Source to) throws InvalidValueException { |
479 | 0 | Transaction t = new TransactionImpl(); |
480 | |
|
481 | 0 | t.setDate(date); |
482 | 0 | t.setDescription(description); |
483 | 0 | t.setAmount(amount); |
484 | 0 | t.setFrom(from); |
485 | 0 | t.setTo(to); |
486 | |
|
487 | 0 | return t; |
488 | |
} |
489 | |
|
490 | |
|
491 | |
|
492 | |
|
493 | |
|
494 | |
|
495 | |
|
496 | |
|
497 | |
|
498 | |
|
499 | |
public static File getAutoSaveLocation(File baseFile){ |
500 | 3034 | if (baseFile == null) |
501 | 3034 | return OperatingSystemUtil.getUserFile("Buddi", "AutosaveData" + Const.AUTOSAVE_FILE_EXTENSION); |
502 | |
else { |
503 | 0 | String autoSaveLocationString = baseFile.getAbsolutePath(); |
504 | 0 | autoSaveLocationString = |
505 | |
autoSaveLocationString.replaceAll( |
506 | |
Const.DATA_FILE_EXTENSION + "$", ""); |
507 | 0 | autoSaveLocationString = |
508 | |
autoSaveLocationString |
509 | |
+ Const.AUTOSAVE_FILE_EXTENSION; |
510 | 0 | return new File(autoSaveLocationString); |
511 | |
} |
512 | |
} |
513 | |
} |