Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/pjsofts/eurobudget/beans/Transaction.java


1   package com.pjsofts.eurobudget.beans;
2   
3   import com.pjsofts.eurobudget.EBConstants;
4   import com.pjsofts.eurobudget.EuroBudget;
5   import java.awt.datatransfer.DataFlavor;
6   import java.awt.datatransfer.Transferable;
7   import java.awt.datatransfer.UnsupportedFlavorException;
8   import java.io.ByteArrayInputStream;
9   import java.io.IOException;
10  import java.io.Serializable;
11  import java.util.*;
12  import java.util.logging.Level;
13  import javax.swing.Icon;
14  
15  /**
16   * Java Bean
17   * Operation, Payment ...
18   * Any move of money managed by this software
19   * Can't be abstract cause Bean but this class should normally never be instanciated directly.
20   * (except to create some tmp transaction that serves to make a search in a list of txns)
21   */
22  public class Transaction extends Object implements java.lang.Comparable, Transferable, Serializable
23  {
24      private static final long serialVersionUID = 2398412327203363556L;
25      private static final transient ResourceBundle i18n = EBConstants.i18n;
26  
27      // properties names used by property support
28      public static final transient String PROPERTY_AMOUNT = "amount";
29      public static final transient String PROPERTY_DATE = "date";
30      public static final transient String PROPERTY_EFFECTIVE_DATE = "effectivedate";
31      public static final transient String PROPERTY_CREATION_DATE = "creationdate";
32      public static final transient String PROPERTY_CATEGORYPAIR = "categorypair";
33      public static final transient String PROPERTY_CATEGORY = "category";
34      public static final transient String PROPERTY_SUBCATEGORY = "subcategory";
35      public static final transient String PROPERTY_DETAILS = "details";
36      public static final transient String PROPERTY_NUMBER = "number";
37      //public static final String PROPERTY_ENTITY = "entity";
38      public static final transient String PROPERTY_TO_ENTITY = "toentity";
39      public static final transient String PROPERTY_FROM_ENTITY = "fromentity";
40      public static final transient String PROPERTY_ACCOUNT = "account";
41      public static final transient String PROPERTY_CHECKED = "checked";
42      public static final transient String PROPERTY_VERIFIED = "verified";
43      public static final transient String PROPERTY_SRC_ACCOUNT = "sourceaccount";
44      public static final transient String PROPERTY_TARGET_ACCOUNT = "targetaccount";
45      // not really properties but some shortcuts
46      public static final transient String PROPERTY_DIRECTION = "direction";
47      public static final transient String PROPERTY_ENTITY = "entity";
48      public static final transient String PROPERTY_TYPE = "type";
49      public static final transient String PROPERTY_TYPE_ICON = "typeicon";
50      public static final transient String PROPERTY_SPLITTED = "split";
51      
52      public static final transient DataFlavor DATA_FLAVOR = new DataFlavor(Transaction.class,"Transaction"); 
53      
54      /** used to reset dates */
55      private static final transient Calendar cal = Calendar.getInstance();
56  
57      
58      /** Holds value of property date.(Time should be reset to same value for all date) 
59       * @serial
60       */
61      private Date date = null;
62      /** Date on which the amount is really on our account 
63       * @serial
64       */
65      private Date effectiveDate= null;
66      /** Date & Time on which the user enter this transaction (date de saisie, util pour qq tri)
67       * @serial
68       */
69      private Date creationDate = null;
70      
71      /** Holds value of property amount. 
72       * @serial
73       */
74      private double amount = 0.0d;
75      
76      /** Holds value of property verified.
77       * It means owner make verification from the bank monthly receipe until this item
78       * @serial
79       */
80      private boolean verified = false;
81      
82      /** Holds value of property checked.
83       * It means if this transaction has been checked by the owner (thanks to a ticket,note or memory)
84       * @serial
85       */
86      private boolean checked = false;
87      
88      /** Holds value of property libelle.(getting info from bank file)
89       * Nature de l'opération
90       */
91      //private String libelle;
92      
93      /** Holds value of property details. (comment, details d'écriture )
94       * Commentaire ou détails
95       * @serial
96       */
97      private String details = null;
98      
99      /** may be used later on ... 
100      * @serial
101      */
102     private transient BitSet flags = null;
103     
104     /** Holds value of property category. (revenues/costs)
105      * Warning each txn should have its own instance of CategoryPair
106      * (or we will have bad bugs !!)
107      * @serial
108      */
109     private CategoryPair categoryPair = new CategoryPair();
110     
111     /** another category to look in many other ways , not used yet
112      */
113     private transient Category[] classification;   // or CategoryPair ???
114 
115     /** contains other transaction (for split txns) */
116     private List txnList = null;//save memory ...//new ArrayList();
117     
118     /** default constructor for a bean
119      * set the creation date to now
120      */
121     public Transaction(){
122         super();
123         this.creationDate = new Date();
124     }
125     
126     /** copy/convertor constructor (this accepts to create a Check from a Virement !)*/
127     public Transaction(Transaction t){
128         this();
129         this.copy(t);        // copy also the creation date ??
130     }
131     
132     /** Not a real copy constructor, but a convertor
133      * need to be overridden by each child (that add a property)
134      * this may not copy all properties to respect our human rules...
135      * Used in convertion action of Payment to Versement ..
136      */
137     public void copy(Transaction t) {
138         setDate(t.getDate());
139         setAmount(t.getAmount());
140         setDetails(t.getDetails());
141         setChecked(t.isChecked());
142         setVerified(t.isVerified());
143         setEffectiveDate(t.getEffectiveDate());
144         //setEntityName(t.getEntityName());
145         //warning categories may be wrong if change from cost to revenue (payment to versement)
146         //        setCategory(t.getCategory());
147         //        setSubCategory(t.getSubCategory());
148         // copy also the creation date ?? not possible with final
149         //this.creationDate = t.getCreationDate();
150     }
151 
152     /** 
153      * Copy field that are null or empty for a template transaction.
154      * (Works on details, category pair, amount)
155      * need to be overridden by each child (that add a property)
156      * this may not copy all properties to respect our human rules...
157      */
158     public void copyAutoEntry(Transaction t) {
159         // doesn't copy any date property
160         if ( this.getDetails() == null || this.getDetails().length() == 0 ) {
161             this.setDetails(t.getDetails());
162         }
163         if ( this.getCategoryPair() == null )
164             this.setCategoryPair(new CategoryPair());
165         if ( this.getCategoryPair().isNull() ) {
166             this.getCategoryPair().copy(t.getCategoryPair());// problem here (how to set a ctg ?)
167         }
168         if ( this.getAmount() == 0d ) {
169             this.setAmount(t.getAmount());
170         }
171     }
172     
173     /**
174      * @return true if transaction is splitted in sub-txn */
175     public boolean isSplitted() {
176         return this.txnList != null && !this.txnList.isEmpty();
177     }
178     
179     /** convenient constructor
180      * used by child public constructor but here should not be public */
181     protected Transaction(Date date, double amount, String details,
182     CategoryPair cat) {
183         this();
184         setDate(date);
185         setAmount(amount);
186         //setLibelle(lib);
187         setDetails(details);
188         setCategoryPair(cat);
189     }
190     
191     /** Getter for property date.
192      * @return Value of property date.
193      */
194     public Date getDate() {
195         return this.date;
196     }
197     
198     /** Setter for property date.
199      * @param date New value of property date. will remove the time part (set to Zero hour,mn,sec)
200      * if null is given, set to new Date !
201      */
202     public void setDate(Date date) {
203         if ( date == null ) date = new Date();
204         synchronized (cal) {
205             cal.setTime(date);
206             cal.set(Calendar.HOUR,0);
207             cal.set(Calendar.MINUTE,0);
208             cal.set(Calendar.SECOND,0);
209             cal.set(Calendar.MILLISECOND,0);
210             this.date = cal.getTime();
211         }
212         if ( getEffectiveDate() == null )
213             setEffectiveDate(this.date);
214     }
215     
216     /** Getter for property amount.
217      * @return Value of property amount.
218      */
219     public double getAmount() {
220         return this.amount;
221     }
222     
223     /** Setter for property amount.
224      * @param amount New value of property amount.
225      */
226     public void setAmount(double amount) {
227         this.amount = Math.abs(amount);//amount;
228     }
229     
230     /** Getter for property verified.
231      * @return Value of property verified.
232      */
233     public boolean isVerified() {
234         return this.verified;
235     }
236     
237     /** Setter for property verified.
238      * @param verified New value of property verified.
239      */
240     public void setVerified(boolean verified) {
241         this.verified = verified;
242     }
243     
244     /** Getter for property checked.
245      * @return Value of property checked.
246      */
247     public boolean isChecked() {
248         return this.checked;
249     }
250     
251     /** Setter for property checked.
252      * @param checked New value of property checked.
253      */
254     public void setChecked(boolean checked) {
255         this.checked = checked;
256     }
257     
258     //    /** Getter for property libelle.
259     //     * @return Value of property libelle.
260     //     */
261     //    public String getLibelle() {
262     //        return this.libelle;
263     //    }
264     //
265     //    /** Setter for property libelle.
266     //     * @param libelle New value of property libelle.
267     //     */
268     //    public void setLibelle(String libelle) {
269     //        this.libelle = libelle;
270     //    }
271     
272     /** Getter for property details.
273      * @return Value of property details.
274      */
275     public String getDetails() {
276         return this.details;
277     }
278     
279     /** Setter for property details.
280      * @param details New value of property details.
281      */
282     public void setDetails(String details) {
283         this.details = details;
284     }
285     
286     /** Getter for property category.
287      * @return Value of property category.
288      */
289     public CategoryPair getCategoryPair() {
290         return this.categoryPair;
291     }
292     
293     /**
294      * Don't use this call normally (use copyCategoryPair instead) to avoid transactions to share 
295      * the same category pair. 
296      * Setter for property category.
297      *  warning, used for serialization but if you need to copy a category pair or change its main or sub ctg, use other methods.
298      * (like getCategoryPair().setMain(categoryPair.getMain()))
299      * @param category New value of property category.
300      *
301      */
302 //    public void setCategoryPair(CategoryPair categoryPair) {
303 //        this.categoryPair = categoryPair;
304 //    }
305 
306     /**
307      * Complex behavior:
308      * - if given pair is special set to it
309      * - if current pair is null or special create a new one
310      * - if given pair is not special, just copy it.
311      * Setter for property category.
312      *  warning, used for serialization but if you need to copy a category pair or change its main or sub ctg, use other methods.
313      * (like getCategoryPair().setMain(categoryPair.getMain()))
314      * @param category New value of property category.
315      *
316      */
317     public void setCategoryPair(CategoryPair categoryPair) {
318         if ( categoryPair == null ) { 
319             this.categoryPair = null;
320         } else if ( categoryPair.isSpecial() ) {
321             // try to keep only one instance of special pair
322             this.categoryPair = categoryPair;
323         } else {
324             if ( this.categoryPair == null || this.categoryPair.isSpecial() ) {
325                 this.categoryPair = new CategoryPair();
326             }
327             this.categoryPair.copy(categoryPair);
328         }
329     }
330     
331     
332     /** not static as it should be override
333      * @return short string representing this kind of transaction */
334     public String getType() {
335         return i18n.getString("Transaction");
336     }
337     
338     /**
339      * @return icon representing this kind of transaction 
340      * warning implementation should try to refer to same instance of icon whenever possible.
341      */
342     public Icon getTypeIcon() {
343         return null;
344     }
345     
346     /** @return string representing what should be in the "to/from" column of txn table
347      * null meaning the owner
348      */
349     public String getDirectionTip() {
350         return null;
351     }
352     
353     /** @return the real amount eg. the one which service in computation of balance
354      * per default the negative amount
355      */
356     public double getRealAmount() {
357         return -getAmount();
358     }
359     
360     /** @return Long number that should identify this transaction (check number or else) or null if no info
361      */
362     public Long getID() {
363         return null;
364     }
365     
366     /** Getter for property effective_date.
367      * @return Value of property effective_date.
368      */
369     public Date getEffectiveDate() {
370         return effectiveDate;
371     }
372     
373     /** Setter for property effective_date.
374      * @param effective_date New value of property effective_date.
375      */
376     public void setEffectiveDate(Date effective_date) {
377         this.effectiveDate = effective_date;
378     }
379     /**
380      * Note: this class has a natural ordering that is
381      * inconsistent with equals !
382      * should accept date null values, null means before anything
383      * Ordered on : Date,CreationDate
384      * @return 1: obj<this,; 0 equals(exact same txn), -1: this<obj
385      * Warning: if == 0 doesn't mean same txn then many algorythm will fail (go to, search,contains...)
386      */
387     public int compareTo(Object obj) {
388         if ( obj instanceof Transaction ) {
389             Transaction tobj = (Transaction)obj;
390             if ( obj == null || tobj.getDate() == null ) return 1;
391             if ( this.getDate() == null ) return -1;
392             // look for same date (yy,mm,dd) is not done as time should have resetted for all dates
393             int compDate = this.getDate().compareTo(tobj.getDate());
394             if ( compDate != 0 ) return compDate;
395             //same date, compare on Creation date
396             if ( tobj.getCreationDate() == null ) return 1;
397             if ( this.getCreationDate() == null ) return -1;
398             int compWDate = this.getCreationDate().compareTo(tobj.getCreationDate());
399             //should not happen normally (each txn should have its own creation date but
400             // to avoid bugs we put a last ordering on hashcode...
401             if ( compWDate != 0 ) return compWDate;
402             else if ( this.hashCode() < tobj.hashCode() ) return -1;
403             else if ( this.hashCode() > tobj.hashCode() ) return 1;
404             else return 0;//in that case same instance !
405         } else {
406             throw new java.lang.IllegalArgumentException(i18n.getString("error_Compare_only_to_Transaction"));
407         }
408     }
409     /**
410      * Convenient method when iterating over transactions and avoiding instanceof
411      * @return the related entity(if any) or null (if doesn't)
412      *  only Payment and Versment should return something != null
413      * Note that Transaction doesn't have any entity property
414      */
415     public Entity getEntity(){
416         return null;
417     }
418     
419     /**
420      * Convenient method when iterating over transactions and avoiding instanceof
421      * @return name of the related entity(person) or null if doesn)
422      *  only Payment and Versment should return something != null
423      * Note that Transaction doesn't have any entityName property
424      */
425     public String getEntityName(){
426         return null;
427     }
428     
429     /** Getter for property CreationDate.
430      * @return Value of property CreationDate.
431      */
432     public Date getCreationDate() {
433         return creationDate;
434     }
435     
436     /**
437      * Should not be called manually, only serve to reload a bean.
438      * Setter for property creationDate.
439      * @param creationDate New value of property creationDate.
440      */
441     public void setCreationDate(Date creationDate) {
442         this.creationDate = creationDate;
443     }
444     
445     /** @return a cvs string (text data separated with ;) */
446     public String toStringCSV() {
447         // separated with ; or ,
448         // ? replace simple quote by double quote if needed ... we assume its not neccesary yet
449         return toString("\"", "\";", false);
450     }
451     
452     /** @return an other excel format tab text format*/
453     public String toStringTAB() {
454         return toString("", "\t", false);
455     }
456 
457     /** @return text/xml format*/
458     public String toStringXML() {
459         return toString("", "\n", true);
460     }
461     
462     /** 
463      * @param p prefix
464      * @param s suffix
465      * @return text describing each fields, with prefix and suffix string
466      */
467     private String toString(String p, String s, boolean xml) {
468         // separated with tab
469         StringBuffer sb = new StringBuffer();
470         append(sb,xml,PROPERTY_DATE,getDate(),p,s);
471         append(sb,xml,PROPERTY_AMOUNT,new Double(getRealAmount()),p,s);
472         append(sb,xml,PROPERTY_TYPE,getType(),p,s);
473         append(sb,xml,PROPERTY_FROM_ENTITY,getEntityName(),p,s);
474         append(sb,xml,PROPERTY_CATEGORYPAIR,getCategoryPair(),p,s);
475         append(sb,xml,PROPERTY_NUMBER,getID(),p,s);
476         append(sb,xml,PROPERTY_DETAILS,getDetails(),p,s);
477         append(sb,xml,PROPERTY_DIRECTION,getDirectionTip(),p,s);
478         append(sb,xml,PROPERTY_EFFECTIVE_DATE,getEffectiveDate(),p,s);
479         append(sb,xml,PROPERTY_CHECKED,(isChecked() ? i18n.getString("Checked") : i18n.getString("Unchecked")),p,s);
480         append(sb,xml,PROPERTY_VERIFIED,(isVerified()? i18n.getString("Verified") : i18n.getString("Unverified")),p,s);
481         append(sb,xml,PROPERTY_SPLITTED,(isSplitted()? i18n.getString("Splitted") : i18n.getString("Unsplitted")),p,s);
482         append(sb,xml,PROPERTY_CREATION_DATE,getCreationDate(),p,s);
483         return sb.toString();
484     }
485     
486     /**
487      *
488      * param tag name of xml tag
489      */
490     private void append(StringBuffer sb, boolean xml, String tag, Object value, String prefix, String suffix) {
491         sb.append(prefix);
492         if ( xml ) sb.append("<").append(tag).append(">");
493         sb.append(String.valueOf(value));
494         if ( xml ) sb.append("</").append(tag).append(">");
495         sb.append(suffix);
496     }
497     
498     /**
499      * Returns an object which represents the data to be transferred.  The class
500      * of the object returned is defined by the representation class of the flavor.
501      *
502      * @param flavor the requested flavor for the data
503      * @see DataFlavor#getRepresentationClass
504      * @exception IOException                if the data is no longer available
505      *             in the requested flavor.
506      * @exception UnsupportedFlavorException if the requested data flavor is
507      *             not supported.
508      */
509     public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
510         if ( !isDataFlavorSupported(flavor) ) {
511             throw new UnsupportedFlavorException(flavor);
512         }
513         if ( flavor.getRepresentationClass().equals(Transaction.class) ) {
514 //            //representationClass = representationClass
515 //            //mimeType            = application/x-java-serialized-object
516             return this.getPasteClone();
517         } else if ( flavor.isMimeTypeEqual("text/csv") ) {
518             String s = this.toStringCSV();
519 //            StringReader sr = new StringReader(s);
520 //            sr.read(
521 //            new InputStream().
522             return new ByteArrayInputStream(s.getBytes("UTF-8"));
523         } else if ( flavor.isMimeTypeEqual("text/xml") ) {
524             return new ByteArrayInputStream(this.toStringXML().getBytes("UTF-8"));
525 // Not easy to create :            
526 //        } else if ( flavor == DataFlavor.getTextPlainUnicodeFlavor()) {//unicode one
527 //            //return new ByteArrayInputStream(this.toStringTAB().getBytes());
528 //   Charset(System.getProperty("unicode charsets")).getEncoder();            
529 //            Reader reader = flavor.getReaderForText(this.toStringTAB());
530 //            return reader;
531         } else {
532             //just text DataFlavor.stringFlavor
533             return this.toStringTAB();
534         }
535     }
536     
537     /**
538      * Returns an array of DataFlavor objects indicating the flavors the data
539      * can be provided in.  The array should be ordered according to preference
540      * for providing the data (from most richly descriptive to least descriptive).
541      * Order:
542      *  Transaction instance, text/csv, text/plain, String
543      * @return an array of data flavors in which this data can be transferred
544      */
545     public DataFlavor[] getTransferDataFlavors() {
546         Vector v = new Vector();
547         try {
548             v.add(new DataFlavor("text/csv") );
549             v.add(new DataFlavor("text/xml") );
550         } catch (ClassNotFoundException e) {
551             //e.printStackTrace();
552             // try to stay independant of other classes:
553             EuroBudget.logException(e);
554         }
555 //v.add(DataFlavor.getTextPlainUnicodeFlavor());
556         //representationClass = java.io.InputStream
557         // mimetype  = "text/plain;charset=<platform default Unicode encoding>"
558         //The Win32 Sun implementations use the encoding utf-16le. The Solaris and Linux Sun implementations use the encoding iso-10646-ucs-2.
559         v.add(DATA_FLAVOR);//in the endf or others apps
560         v.add(DataFlavor.stringFlavor);
561         //representationClass = representationClass
562         //mimeType            = application/x-java-serialized-object
563         return (DataFlavor[])v.toArray(new DataFlavor[v.size()]);
564     }
565     
566     /**
567      * Returns whether or not the specified data flavor is supported for
568      * this object.
569      * @param flavor the requested flavor for the data
570      * @return boolean indicating whether or not the data flavor is supported
571      */
572     public boolean isDataFlavorSupported(DataFlavor flavor) {
573         List list = Arrays.asList(getTransferDataFlavors());
574         return list.contains(flavor);
575     }
576     
577     /** Getter for property flags.
578      * @return Value of property flags.
579      */
580     public BitSet getFlags() {
581         return flags;
582     }
583     
584     /** Setter for property flags.
585      * @param flags New value of property flags.
586      */
587     public void setFlags(BitSet flags) {
588         this.flags = flags;
589     }
590     
591     /** Getter for property classification.
592      * @return Value of property classification.
593      */
594     public com.pjsofts.eurobudget.beans.Category[] getClassification() {
595         return this.classification;
596     }
597     
598     /** Setter for property classification.
599      * @param classification New value of property classification.
600      */
601     public void setClassification(com.pjsofts.eurobudget.beans.Category[] classification) {
602         this.classification = classification;
603     }
604     
605     /** Returns a string representation of the object. In general, the
606      * <code>toString</code> method returns a string that
607      * "textually represents" this object. The result should
608      * be a concise but informative representation that is easy for a
609      * person to read.
610      * It is recommended that all subclasses override this method.
611      * <p>
612      * The <code>toString</code> method for class <code>Object</code>
613      * returns a string consisting of the name of the class of which the
614      * object is an instance, the at-sign character `<code>@</code>', and
615      * the unsigned hexadecimal representation of the hash code of the
616      * object. In other words, this method returns a string equal to the
617      * value of:
618      * <blockquote>
619      * <pre>
620      * getClass().getName() + '@' + Integer.toHexString(hashCode())
621      * </pre></blockquote>
622      *
623      * @return  a string representation of the object.
624      *
625      */
626     public String toString() {
627         String retValue;        
628         retValue = super.toString() + "("+toStringCSV()+")";
629         return retValue;
630     }
631     
632     /** Getter for property txnList.
633      * @return Value of property txnList.
634      *
635      */
636     public java.util.List getTxnList() {
637         return txnList;
638     }
639     
640     /** Setter for property txnList.
641      * @param txnList New value of property txnList.
642      *
643      */
644     public void setTxnList(java.util.List txnList) {
645         this.txnList = txnList;
646     }
647     
648     /**
649      * Convenient method when iterating over transactions and avoiding instanceof
650      *  only Payment and Versment should return something != null
651      * Note that Transaction doesn't have any entityName property
652      */
653     // not good as Entity is not a String and this class can't make a link between the two
654     //    public void setEntityName(String name) {
655     //        //any child that has one entity only should override this method
656     //    }
657 
658     /** 
659      * @return shallow copy (not deep copy) of this transaction
660      * new transaction won't be checked, neither verified
661      * clone won't have same creation date.
662      */
663     public Transaction getPasteClone() {
664         Transaction t = null;
665         try {
666             t = (Transaction)this.getClass().newInstance();//better than clone()
667             //t.setCreationDate(new Date());
668             t.setChecked(false);
669             t.setVerified(false);
670             t.setDate(getDate());
671             t.setAmount(getAmount());
672             t.setDetails(getDetails());
673             t.setEffectiveDate(getEffectiveDate());        
674             //t.setCategoryPair(new CategoryPair());
675             t.getCategoryPair().copy(this.getCategoryPair());
676             if ( this.isSplitted() ) {
677                 List newList = getPasteClone(this.getTxnList());                
678                 t.setTxnList(newList);
679                 //System.out.println("oldList= "+this.getTxnList().size()+" new="+t.getTxnList().size());
680             }
681         } catch (Exception cne) {
682             cne.printStackTrace();//no call to EuroBudget.log(Level.CRITICAL, cne);
683             EuroBudget.log(Level.SEVERE, cne);
684         }
685         return t;
686     }
687     
688     /**
689      * @param list of Transaction 
690      * @return new list of past cloned transaction 
691      *  (if given list is null, return null)
692      */
693      public static List getPasteClone(List list) {
694         if ( list == null ) return null;
695         List result = new ArrayList(list.size());
696         for (Iterator it=list.iterator(); it.hasNext(); ) {
697             Transaction t = (Transaction)it.next();
698             result.add(t.getPasteClone());
699         }
700         System.out.println("PasteCloneList oldList= "+list.size()+" new="+result.size());
701         return result;
702     }
703 }
704