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