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