JAL-1648 House keeping
[jalview.git] / src / jalview / io / cache / JvCacheableInputBox.java
1 package jalview.io.cache;
2
3 import jalview.util.MessageManager;
4
5 import java.awt.Color;
6 import java.awt.Dimension;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.util.ArrayList;
10 import java.util.Collections;
11 import java.util.LinkedHashSet;
12 import java.util.List;
13 import java.util.Set;
14
15 import javax.swing.BorderFactory;
16 import javax.swing.JComboBox;
17 import javax.swing.JLabel;
18 import javax.swing.JMenuItem;
19 import javax.swing.JPanel;
20 import javax.swing.JPopupMenu;
21 import javax.swing.JTextField;
22 import javax.swing.SwingUtilities;
23 import javax.swing.text.AttributeSet;
24 import javax.swing.text.BadLocationException;
25 import javax.swing.text.PlainDocument;
26
27 public class JvCacheableInputBox<E> extends JComboBox<String>
28 {
29
30   private static final long serialVersionUID = 5774610435079326695L;
31
32   private String cacheKey;
33
34   private AppCache appCache;
35
36   JPanel pnlDefaultCache = new JPanel();
37
38   JLabel lblDefaultCacheSize = new JLabel();
39
40   JTextField txtDefaultCacheSize = new JTextField();
41
42   JPopupMenu popup = new JPopupMenu();
43
44   JMenuItem menuItemClearCache = new JMenuItem();
45
46   final static int INPUT_LIMIT = 2;
47
48
49   public JvCacheableInputBox(String cacheKey)
50   {
51     super();
52     this.cacheKey = cacheKey;
53     setEditable(true);
54     setPrototypeDisplayValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
55     appCache = AppCache.getInstance();
56     initCachePopupMenu();
57     appCache.initCache(cacheKey);
58     updateCache();
59   }
60
61
62   /**
63    * Initialise this cache's pop-up menu
64    */
65   private void initCachePopupMenu()
66   {
67     pnlDefaultCache.setBackground(Color.WHITE);
68     // pad panel so as to align with other menu items
69     pnlDefaultCache.setBorder(BorderFactory.createEmptyBorder(0, 19, 0, 0));
70     txtDefaultCacheSize.setPreferredSize(new Dimension(45, 20));
71     lblDefaultCacheSize.setText(MessageManager
72             .getString("label.default_cache_size"));
73     // Force input to accept only Integer entries up to length - INPUT_LIMIT
74     txtDefaultCacheSize.setDocument(new PlainDocument()
75     {
76       private static final long serialVersionUID = 1L;
77
78       @Override
79       public void insertString(int offs, String str, AttributeSet a)
80               throws BadLocationException
81       {
82         if (getLength() + str.length() <= INPUT_LIMIT && isInteger(str))
83         {
84           super.insertString(offs, str, a);
85         }
86       }
87     });
88     txtDefaultCacheSize.setText(appCache.getCacheLmit(cacheKey));
89     pnlDefaultCache.add(lblDefaultCacheSize);
90     pnlDefaultCache.add(txtDefaultCacheSize);
91     menuItemClearCache.setText(MessageManager
92             .getString("action.clear_cached_items"));
93     menuItemClearCache.addActionListener(new ActionListener()
94     {
95       @Override
96       public void actionPerformed(ActionEvent e)
97       {
98         // System.out.println(">>>>> Clear cache items");
99         setSelectedItem("");
100         appCache.deleteCacheItems(cacheKey);
101         updateCache();
102       }
103     });
104
105     popup.insert(pnlDefaultCache, 0);
106     popup.add(menuItemClearCache);
107     setComponentPopupMenu(popup);
108     add(popup);
109   }
110
111   /**
112    * Answers true if input text is an integer
113    * 
114    * @param text
115    * @return
116    */
117   static boolean isInteger(String text)
118   {
119     try
120     {
121       Integer.parseInt(text);
122       return true;
123     } catch (NumberFormatException e)
124     {
125       return false;
126     }
127   }
128
129   /**
130    * Method called to update the cache with the last user input
131    */
132   public void updateCache()
133   {
134     SwingUtilities.invokeLater(new Runnable()
135     {
136       @Override
137       public void run()
138       {
139         int cacheLimit = appCache.updateCacheLimit(cacheKey,
140                 txtDefaultCacheSize.getText());
141         String userInput = getUserInput();
142         if (userInput != null && !userInput.isEmpty())
143         {
144           LinkedHashSet<String> foundCache = appCache
145                   .getAllCachedItemsFor(cacheKey);
146           // remove old cache item so as to place current input at the top of
147           // the result
148           foundCache.remove(userInput);
149           foundCache.add(userInput);
150           appCache.putCache(cacheKey, foundCache);
151         }
152
153         String lastSearch = userInput;
154         if (getItemCount() > 0)
155         {
156           removeAllItems();
157         }
158         Set<String> cacheItems = appCache.getAllCachedItemsFor(cacheKey);
159         List<String> reversedCacheItems = new ArrayList<String>();
160         reversedCacheItems.addAll(cacheItems);
161         cacheItems = null;
162         Collections.reverse(reversedCacheItems);
163         if (lastSearch.isEmpty())
164         {
165           addItem("");
166         }
167
168         if (reversedCacheItems != null && !reversedCacheItems.isEmpty())
169         {
170           LinkedHashSet<String> foundCache = appCache
171                   .getAllCachedItemsFor(cacheKey);
172           boolean prune = reversedCacheItems.size() > cacheLimit;
173           int count = 1;
174           boolean limitExceeded = false;
175           for (String cacheItem : reversedCacheItems)
176           {
177             limitExceeded = (count++ > cacheLimit);
178             if (prune)
179             {
180               if (limitExceeded)
181               {
182                 foundCache.remove(cacheItem);
183               }
184               else
185               {
186                 addItem(cacheItem);
187               }
188             }
189             else
190             {
191               addItem(cacheItem);
192             }
193           }
194           appCache.putCache(cacheKey, foundCache);
195         }
196         setSelectedItem(lastSearch.isEmpty() ? "" : lastSearch);
197       }
198     });
199   }
200
201
202   /**
203    * This method should be called to persist the in-memory cache when this
204    * components parent frame is closed / exited
205    */
206   public void persistCache()
207   {
208     appCache.persistCache(cacheKey);
209     appCache.updateCacheLimit(cacheKey, txtDefaultCacheSize.getText());
210   }
211
212   /**
213    * Method to obtain input text from the cache box
214    * 
215    * @return
216    */
217   public String getUserInput()
218   {
219     return getEditor().getItem() == null ? "" : getEditor().getItem()
220             .toString().trim();
221   }
222
223 }