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