OSDN Git Service

Reworked search logic to better handle multiple positive/negative matching of the...
[neighbornote/NeighborNote.git] / src / cx / fbn / nevernote / sql / REnSearch.java
1 /*\r
2  * This file is part of NixNote \r
3  * Copyright 2009 Randy Baumgarte\r
4  * \r
5  * This file may be licensed under the terms of of the\r
6  * GNU General Public License Version 2 (the ``GPL'').\r
7  *\r
8  * Software distributed under the License is distributed\r
9  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either\r
10  * express or implied. See the GPL for the specific language\r
11  * governing rights and limitations.\r
12  *\r
13  * You should have received a copy of the GPL along with this\r
14  * program. If not, go to http://www.gnu.org/licenses/gpl.html\r
15  * or write to the Free Software Foundation, Inc.,\r
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\r
17  *\r
18 */\r
19 \r
20 \r
21 package cx.fbn.nevernote.sql;\r
22 \r
23 import java.text.SimpleDateFormat;\r
24 import java.util.ArrayList;\r
25 import java.util.Calendar;\r
26 import java.util.GregorianCalendar;\r
27 import java.util.List;\r
28 import java.util.regex.Pattern;\r
29 \r
30 import org.apache.commons.lang3.StringEscapeUtils;\r
31 \r
32 import com.evernote.edam.type.Note;\r
33 import com.evernote.edam.type.Notebook;\r
34 import com.evernote.edam.type.Tag;\r
35 \r
36 import cx.fbn.nevernote.Global;\r
37 import cx.fbn.nevernote.sql.driver.NSqlQuery;\r
38 import cx.fbn.nevernote.utilities.ApplicationLogger;\r
39 \r
40 public class REnSearch {\r
41         \r
42         private final List<String>      searchWords;\r
43         private final List<String>  searchPhrases;\r
44         private final List<String>      notebooks;\r
45         private final List<String>      tags;\r
46         private final List<String>      intitle;\r
47         private final List<String>      created;\r
48         private final List<String>      updated;\r
49         private final List<String>      resource;\r
50         private final List<String>      subjectDate;\r
51         private final List<String>      longitude;\r
52         private final List<String>      latitude;\r
53         private final List<String>      altitude;\r
54         private final List<String>      author;\r
55         private final List<String>      source;\r
56         private final List<String>      sourceApplication;\r
57         private final List<String>      recoType;\r
58         private final List<String>      todo;\r
59         private final List<String>  stack;\r
60         private final List<Tag>         tagIndex;\r
61         private final ApplicationLogger logger;\r
62 //      private final DatabaseConnection db;\r
63         private boolean any;\r
64         private int minimumRecognitionWeight = 80;\r
65         private final DatabaseConnection conn;\r
66         \r
67         public REnSearch(DatabaseConnection c, ApplicationLogger l, String s, List<Tag> t, int r) {\r
68                 logger = l;\r
69                 conn = c;\r
70                 tagIndex = t;\r
71                 minimumRecognitionWeight = r;\r
72                 searchWords = new ArrayList<String>();\r
73                 searchPhrases = new ArrayList<String>();\r
74                 notebooks = new ArrayList<String>();\r
75                 tags = new ArrayList<String>();\r
76                 intitle = new ArrayList<String>();\r
77                 created = new  ArrayList<String>();\r
78                 updated = new ArrayList<String>();\r
79                 resource = new ArrayList<String>();\r
80                 subjectDate = new ArrayList<String>();\r
81                 longitude = new ArrayList<String>();\r
82                 latitude = new ArrayList<String>();\r
83                 altitude = new ArrayList<String>();\r
84                 author = new ArrayList<String>();\r
85                 source = new ArrayList<String>();\r
86                 sourceApplication = new ArrayList<String>();\r
87                 recoType = new ArrayList<String>();\r
88                 todo = new ArrayList<String>();\r
89                 any = false;\r
90                 stack = new ArrayList<String>();\r
91                 \r
92                 if (s == null) \r
93                         return;\r
94                 if (s.trim().equals(""))\r
95                         return;\r
96                 \r
97                 resolveSearch(s);\r
98         }\r
99                 \r
100         public List<String> getWords() { return searchWords; }\r
101         public List<String> getNotebooks() { return notebooks; }\r
102         public List<String> getIntitle() {      return intitle; }\r
103         public List<String> getTags() { return tags; }\r
104         public List<String> getResource() {     return resource; }\r
105         public List<String> getAuthor() { return author; }      \r
106         public List<String> getSource() { return source; }      \r
107         public List<String> getSourceApplication() { return sourceApplication; }        \r
108         public List<String> getRecoType() {     return recoType; }      \r
109         public List<String> getToDo() { return todo; }\r
110         public List<String> getLongitude() { return longitude; }\r
111         public List<String> getLatitude() { return latitude; }\r
112         public List<String> getAltitude() { return altitude; }\r
113         public List<String> getCreated() { return created; }\r
114         public List<String> getUpdated() { return updated; }\r
115         public List<String> getSubjectDate() { return subjectDate; }\r
116         public List<String> getStack() { return stack; }\r
117 \r
118         // match tag names\r
119         private boolean matchTagsAll(List<String> tagNames, List<String> list) {\r
120                                 \r
121                 for (int j=0; j<list.size(); j++) {\r
122                         boolean negative = false;\r
123                         negative = false;\r
124                         if (list.get(j).startsWith("-"))\r
125                                 negative = true;\r
126                         int pos = list.get(j).indexOf(":");\r
127                         String filterName = cleanupWord(list.get(j).substring(pos+1));\r
128                         filterName = filterName.replace("*", ".*");   // setup for regular expression pattern match\r
129                         \r
130                         if (tagNames.size() == 0 && !negative)\r
131                                 return false;\r
132                         \r
133                         for (int i=0; i<tagNames.size(); i++) { \r
134                                 boolean matches = Pattern.matches(filterName.toLowerCase(),tagNames.get(i).toLowerCase());\r
135                                 if (!matches && !negative)\r
136                                         return false;\r
137                                 if (matches && negative)\r
138                                         return false;\r
139                         }\r
140                 }\r
141                 return true;\r
142         }\r
143         \r
144         // match tag names\r
145         private boolean matchTagsAny(List<String> tagNames, List<String> list) {\r
146                 if (list.size() == 0)\r
147                         return true;\r
148                 \r
149                 boolean negative = false;               \r
150 \r
151                 for (int j=0; j<list.size(); j++) {\r
152                         negative = false;\r
153                         if (list.get(j).startsWith("-"))\r
154                                 negative = true;\r
155                         int pos = list.get(j).indexOf(":");\r
156                         String filterName = cleanupWord(list.get(j).substring(pos+1));\r
157                         filterName = filterName.replace("*", ".*");   // setup for regular expression pattern match\r
158                         \r
159                         if (tagNames.size() == 0 && !negative)\r
160                                 return false;\r
161 \r
162                         for (int i=0; i<tagNames.size(); i++) {         \r
163                                 boolean matches = Pattern.matches(filterName.toLowerCase(),tagNames.get(i).toLowerCase());\r
164                                 if (!matches && !negative)\r
165                                         return false;\r
166                         }\r
167                 }\r
168                 return true;\r
169         }\r
170         \r
171         \r
172         // Match notebooks in search terms against notes\r
173         private boolean matchNotebook(String guid) {\r
174                 if (getNotebooks().size() == 0)\r
175                         return true;\r
176                 NotebookTable bookTable = new NotebookTable(logger, conn);\r
177                 List<Notebook> books = bookTable.getAll();\r
178 \r
179                 String name = new String("");\r
180                 for (int i=0; i<books.size(); i++) {\r
181                         if (guid.equalsIgnoreCase(books.get(i).getGuid())) {\r
182                                 name = books.get(i).getName();\r
183                                 i=books.size();\r
184                         }\r
185                 }\r
186                 if (any)\r
187                         return matchListAny(getNotebooks(), name);\r
188                 else\r
189                         return matchListAll(getNotebooks(), name);\r
190         }\r
191         // Match notebooks in search terms against notes\r
192         private boolean matchNotebookStack(String guid) {\r
193                 if (getStack().size() == 0)\r
194                         return true;\r
195                 NotebookTable bookTable = new NotebookTable(logger, conn);\r
196                 List<Notebook> books = bookTable.getAll();\r
197 \r
198                 String name = new String("");\r
199                 for (int i=0; i<books.size(); i++) {\r
200                         if (guid.equalsIgnoreCase(books.get(i).getGuid())) {\r
201                                 name = books.get(i).getStack();\r
202                                 i=books.size();\r
203                         }\r
204                 }\r
205                 if (name == null)\r
206                         name = "";\r
207                 if (any)\r
208                         return matchListAny(getStack(), name);\r
209                 else\r
210                         return matchListAll(getStack(), name);\r
211         }\r
212 \r
213         // Match notebooks in search terms against notes\r
214         private boolean matchListAny(List<String> list, String title) {\r
215                 if (list.size() == 0)\r
216                         return true;\r
217                 boolean negative = false;\r
218                 boolean found = false;\r
219                 for (int i=0; i<list.size(); i++) {\r
220                         int pos = list.get(i).indexOf(":");\r
221                         negative = false;\r
222                         if (list.get(i).startsWith("-"))\r
223                                 negative = true;\r
224                         String filterName = cleanupWord(list.get(i).substring(pos+1));\r
225                         filterName = filterName.replace("*", ".*");   // setup for regular expression pattern match\r
226                         boolean matches = Pattern.matches(filterName.toLowerCase(),title.toLowerCase());\r
227                         if (matches)\r
228                                 found = true;\r
229                 }\r
230                 if (negative)\r
231                         return !found;\r
232                 else\r
233                         return found;\r
234         }\r
235         // Match notebooks in search terms against notes\r
236         private boolean matchContentAny(Note n) {\r
237                 if (todo.size() == 0 && resource.size() == 0 && searchPhrases.size() == 0)\r
238                         return true;\r
239 \r
240                 // pull back the record\r
241                 n = conn.getNoteTable().getNote(n.getGuid(), true, true, false, false, false);\r
242 \r
243                 // Check for search phrases\r
244                 String text = StringEscapeUtils.unescapeHtml4(n.getContent().replaceAll("\\<.*?\\>", "")).toLowerCase();\r
245                 boolean negative = false;\r
246                 for (int i=0; i<searchPhrases.size(); i++) {\r
247                         String phrase = searchPhrases.get(i);\r
248                         if (phrase.startsWith("-")) {\r
249                                 negative = true;\r
250                                 phrase = phrase.substring(1);\r
251                         } else\r
252                                 negative = false;\r
253                         phrase = phrase.substring(1);\r
254                         phrase = phrase.substring(0,phrase.length()-1);\r
255                         if (text.indexOf(phrase)>=0) {\r
256                                 if (negative)\r
257                                         return false;\r
258                                 else\r
259                                         return true;\r
260                         }\r
261                         if (text.indexOf(phrase)<0 && negative)\r
262                                 return true;\r
263                 }\r
264                 \r
265                 for (int i=0; i<todo.size(); i++) {\r
266                         String value = todo.get(i);\r
267                         value = value.replace("\"", "");\r
268                         boolean desiredState;\r
269                         if (!value.endsWith(":false") && !value.endsWith(":true") && !value.endsWith(":*") && !value.endsWith("*"))\r
270                                 return false;\r
271                         if (value.endsWith(":false"))\r
272                                 desiredState = false;\r
273                         else\r
274                                 desiredState = true;\r
275                         if (value.startsWith("-"))\r
276                                 desiredState = !desiredState;\r
277                         int pos = n.getContent().indexOf("<en-todo");\r
278                         if (pos == -1 && value.startsWith("-") && (value.endsWith("*") || value.endsWith(":")))\r
279                                 return true;\r
280                         if (value.endsWith("*"))\r
281                                 return true;\r
282                         while (pos > -1) {\r
283                                 int endPos = n.getContent().indexOf("/>", pos);\r
284                                 String segment = n.getContent().substring(pos, endPos);\r
285                                 boolean currentState;\r
286                                 if (segment.toLowerCase().indexOf("checked=\"true\"") == -1)\r
287                                         currentState = false;\r
288                                 else\r
289                                         currentState = true;\r
290                                 if (desiredState == currentState)\r
291                                         return true;\r
292                                 \r
293                                 pos = n.getContent().indexOf("<en-todo", pos+1);\r
294                         }\r
295                 }\r
296                 \r
297                 // Check resources\r
298                 for (int i=0; i<resource.size(); i++) {\r
299                         String resourceString = resource.get(i);\r
300                         resourceString = resourceString.replace("\"", "");\r
301                         if (resourceString.startsWith("-"))\r
302                                 negative = true;\r
303                         resourceString = resourceString.substring(resourceString.indexOf(":")+1);\r
304                         for (int j=0; j<n.getResourcesSize(); j++) {\r
305                                 boolean match = stringMatch(n.getResources().get(j).getMime(), resourceString, negative);\r
306                                 if (match)\r
307                                         return true;\r
308                         }\r
309                 }\r
310                 return false;\r
311         }\r
312         \r
313         \r
314         // Take the initial search & split it apart\r
315         private void resolveSearch(String search) {\r
316                 List<String> words = new ArrayList<String>();\r
317                 StringBuffer b = new StringBuffer(search);\r
318                 \r
319                 int len = search.length();\r
320                 char nextChar = ' ';\r
321                 boolean quote = false;\r
322                 for (int i=0, j=0; i<len; i++, j++) {\r
323                         if (search.charAt(i)==nextChar && !quote) {\r
324                                 b.setCharAt(j,'\0');\r
325                                 nextChar = ' ';\r
326                         } else {\r
327                                 if (search.charAt(i)=='\"') {\r
328                                         if (!quote) {\r
329                                                 quote=true;\r
330                                         } else {\r
331                                                 quote=false;\r
332                                                 j++;\r
333                                                 b.insert(j, "\0");\r
334                                         }\r
335                                 }\r
336                         }\r
337                         if (((i+2)<len) && search.charAt(i) == '\\') {\r
338                                 i=i+2;\r
339                         }\r
340                 }\r
341                 \r
342                 search = b.toString();\r
343                 int pos = 0;\r
344                 for (int i=0; i<search.length(); i++) {\r
345                         if (search.charAt(i) == '\0') {\r
346                                 search = search.substring(1);\r
347                                 i=0;\r
348                         } else {\r
349                                 pos = search.indexOf('\0');\r
350                                 if (pos > 0) {\r
351                                         words.add(search.substring(0,pos).toLowerCase());\r
352                                         search = search.substring(pos);\r
353                                         i=0;\r
354                                 }\r
355                         }\r
356                 }\r
357                 if (search.charAt(0)=='\0')     \r
358                         words.add(search.substring(1).toLowerCase());\r
359                 else\r
360                         words.add(search.toLowerCase());\r
361                 parseTerms(words);\r
362         }\r
363 \r
364         \r
365         // Parse out individual words into separate lists\r
366         // Supported options\r
367         // Tags\r
368         // Notebooks\r
369         // Intitle\r
370         // author\r
371         // source\r
372         // source application\r
373         // created\r
374         // updated\r
375         // subject date\r
376 \r
377         private void parseTerms(List<String> words) {\r
378                 for (int i=0; i<words.size(); i++) {\r
379                         String word = words.get(i);\r
380                         int pos = word.indexOf(":");\r
381                         if (word.startsWith("any:")) {\r
382                                 any = true;\r
383                                 word = word.substring(4).trim();\r
384                                 pos = word.indexOf(":");\r
385                         }\r
386                         boolean searchPhrase = false;\r
387                         if (pos < 0 && word.indexOf(" ") > 0) {\r
388                                 searchPhrase=true;\r
389                                 searchPhrases.add(word.toLowerCase());\r
390                         }\r
391                         if (!searchPhrase && pos < 0) {\r
392                                 if (word != null && word.length() > 0 && !Global.automaticWildcardSearches())\r
393                                         getWords().add(word); \r
394                                 if (word != null && word.length() > 0 && Global.automaticWildcardSearches()) {\r
395                                         String wildcardWord = word;\r
396                                         if (!wildcardWord.startsWith("*"))\r
397                                                 wildcardWord = "*"+wildcardWord;\r
398                                         if (!wildcardWord.endsWith("*"))\r
399                                                 wildcardWord = wildcardWord+"*";\r
400                                         getWords().add(wildcardWord); \r
401                                 }\r
402 //                              getWords().add("*"+word+"*");           //// WILDCARD\r
403                         }\r
404                         if (word.startsWith("intitle:")) \r
405                                 intitle.add("*"+word+"*");\r
406                         if (word.startsWith("-intitle:")) \r
407                                 intitle.add("*"+word+"*");\r
408                         if (word.startsWith("notebook:")) \r
409                                 notebooks.add(word);\r
410                         if (word.startsWith("-notebook:")) \r
411                                 notebooks.add(word);\r
412                         if (word.startsWith("tag:")) \r
413                                 tags.add(word);\r
414                         if (word.startsWith("-tag:")) \r
415                                 tags.add(word);\r
416                         if (word.startsWith("resource:")) \r
417                                 resource.add(word);\r
418                         if (word.startsWith("-resource:")) \r
419                                 resource.add(word);\r
420                         if (word.startsWith("author:")) \r
421                                 author.add(word);\r
422                         if (word.startsWith("-author:")) \r
423                                 author.add(word);\r
424                         if (word.startsWith("source:")) \r
425                                 source.add(word);\r
426                         if (word.startsWith("-source:")) \r
427                                 source.add(word);\r
428                         if (word.startsWith("sourceapplication:")) \r
429                                 sourceApplication.add(word);\r
430                         if (word.startsWith("-sourceapplication:")) \r
431                                 sourceApplication.add(word);\r
432                         if (word.startsWith("recotype:")) \r
433                                 recoType.add(word);\r
434                         if (word.startsWith("-recotype:")) \r
435                                 recoType.add(word);\r
436                         if (word.startsWith("todo:")) \r
437                                 todo.add(word);\r
438                         if (word.startsWith("-todo:")) \r
439                                 todo.add(word);\r
440                         if (word.startsWith("stack:"))\r
441                                 stack.add(word);\r
442                         if (word.startsWith("-stack:"))\r
443                                 stack.add(word);\r
444 \r
445                         if (word.startsWith("latitude:")) \r
446                                 latitude.add(word);\r
447                         if (word.startsWith("-latitude:")) \r
448                                 latitude.add(word);\r
449                         if (word.startsWith("longitude:")) \r
450                                 longitude.add(word);\r
451                         if (word.startsWith("-longitude:")) \r
452                                 longitude.add(word);\r
453                         if (word.startsWith("altitude:")) \r
454                                 altitude.add(word);\r
455                         if (word.startsWith("-altitude:")) \r
456                                 altitude.add(word);\r
457 \r
458                         if (word.startsWith("created:")) \r
459                                 created.add(word);\r
460                         if (word.startsWith("-created:")) \r
461                                 created.add(word);\r
462                         if (word.startsWith("updated:")) \r
463                                 updated.add(word);\r
464                         if (word.startsWith("-updated:")) \r
465                                 updated.add(word);\r
466                         if (word.startsWith("subjectdate:")) \r
467                                 created.add(word);\r
468                         if (word.startsWith("-subjectdate:")) \r
469                                 created.add(word);\r
470 \r
471                 }\r
472         }\r
473         // Match notebooks in search terms against notes\r
474         private boolean matchListAll(List<String> list, String title) {\r
475                 if (list.size() == 0)\r
476                         return true;\r
477                 boolean negative = false;\r
478                 for (int i=0; i<list.size(); i++) {\r
479                         int pos = list.get(i).indexOf(":");\r
480                         negative = false;\r
481                         if (list.get(i).startsWith("-"))\r
482                                 negative = true;\r
483                         String filterName = cleanupWord(list.get(i).substring(pos+1));\r
484                         filterName = filterName.replace("*", ".*");   // setup for regular expression pattern match\r
485                         boolean matches = Pattern.matches(filterName.toLowerCase(),title.toLowerCase());\r
486                         if (matches && negative)\r
487                                 return false;\r
488                         if (matches && !negative)\r
489                                 return true;\r
490                 }\r
491                 if (negative)\r
492                         return true;\r
493                 else\r
494                         return false;\r
495         }\r
496         // Match notebooks in search terms against notes\r
497         private boolean matchContentAll(Note n) {\r
498                 if (todo.size() == 0 && resource.size() == 0 && searchPhrases.size() == 0)\r
499                         return true;\r
500                 \r
501                 n = conn.getNoteTable().getNote(n.getGuid(), true, true, false, false, false);\r
502                 \r
503                 // Check for search phrases\r
504                 String text = StringEscapeUtils.unescapeHtml4(n.getContent().replaceAll("\\<.*?\\>", "")).toLowerCase();\r
505                 boolean negative = false;\r
506                 for (int i=0; i<searchPhrases.size(); i++) {\r
507                         String phrase = searchPhrases.get(i);\r
508                         if (phrase.startsWith("-")) {\r
509                                 negative = true;\r
510                                 phrase = phrase.substring(1);\r
511                         } else\r
512                                 negative = false;\r
513                         phrase = phrase.substring(1);\r
514                         phrase = phrase.substring(0,phrase.length()-1);\r
515                         if (text.indexOf(phrase)>=0 && negative) {\r
516                                 return false;\r
517                         } \r
518                         if (text.indexOf(phrase) < 0 && !negative)\r
519                                 return false;\r
520                 }\r
521 \r
522                 \r
523                 for (int i=0; i<todo.size(); i++) {\r
524                         String value = todo.get(i);\r
525                         value = value.replace("\"", "");\r
526                         boolean desiredState;\r
527                         if (!value.endsWith(":false") && !value.endsWith(":true") && !value.endsWith(":*") && !value.endsWith("*"))\r
528                                 return false;\r
529                         if (value.endsWith(":false"))\r
530                                 desiredState = false;\r
531                         else\r
532                                 desiredState = true;\r
533                         if (value.startsWith("-"))\r
534                                 desiredState = !desiredState;\r
535                         int pos = n.getContent().indexOf("<en-todo");\r
536                         if (pos == -1 && !value.startsWith("-"))\r
537                                 return false;\r
538                         if (pos > -1 && value.startsWith("-") && (value.endsWith("*") || value.endsWith(":")))\r
539                                 return false;\r
540                         if (pos == -1 && !value.startsWith("-")) \r
541                                 return false;\r
542                         boolean returnTodo = false;\r
543                         while (pos > -1) {\r
544                                 int endPos = n.getContent().indexOf("/>", pos);\r
545                                 String segment = n.getContent().substring(pos, endPos);\r
546                                 boolean currentState;\r
547                                 if (segment.toLowerCase().indexOf("checked=\"true\"") == -1)\r
548                                         currentState = false;\r
549                                 else\r
550                                         currentState = true;\r
551                                 if (desiredState == currentState) \r
552                                         returnTodo = true;\r
553                                 if (value.endsWith("*") || value.endsWith(":"))\r
554                                         returnTodo = true;\r
555                                 \r
556                                 pos = n.getContent().indexOf("<en-todo", pos+1);\r
557                         }\r
558                         if (!returnTodo)\r
559                                 return false;\r
560                 }\r
561                 \r
562                 // Check resources\r
563                 for (int i=0; i<resource.size(); i++) {\r
564                         String resourceString = resource.get(i);\r
565                         resourceString = resourceString.replace("\"", "");\r
566                         negative = false;\r
567                         if (resourceString.startsWith("-"))\r
568                                 negative = true;\r
569                         resourceString = resourceString.substring(resourceString.indexOf(":")+1);\r
570                         if (resourceString.equals(""))\r
571                                 return false;\r
572                         for (int j=0; j<n.getResourcesSize(); j++) {\r
573                                 boolean match = stringMatch(n.getResources().get(j).getMime(), resourceString, negative);\r
574                                 if (!match && !negative)\r
575                                         return false;\r
576                                 if (match && negative) \r
577                                         return false;\r
578                         }\r
579                 }\r
580                 \r
581                 return true;\r
582         }\r
583         \r
584         private boolean stringMatch(String content, String text, boolean negative) {\r
585                 String regex;\r
586                 if (content == null && !negative)\r
587                         return false;\r
588                 if (content == null && negative)\r
589                         return true;\r
590                 \r
591                 if (text.endsWith("*")) {\r
592                         text = text.substring(0,text.length()-1);\r
593                         regex = text;\r
594                 } else {\r
595                         regex = text;\r
596                 }\r
597                 content = content.toLowerCase();\r
598                 regex = regex.toLowerCase();\r
599                 boolean matches = content.startsWith(regex);\r
600                 if (negative)\r
601                         return !matches;\r
602                 return matches;\r
603         }\r
604         \r
605         // Remove odd strings from search terms\r
606         private String cleanupWord(String word) {\r
607                 if (word.startsWith("\""))\r
608                         word = word.substring(1);\r
609                 if (word.endsWith("\""))\r
610             word = word.substring(0,word.length()-1);\r
611                 word = word.replace("\\\"","\"");\r
612                 word = word.replace("\\\\","\\");\r
613                 \r
614                 return word;\r
615         }\r
616 \r
617         \r
618         // Match dates\r
619         private boolean matchDatesAll(List<String> dates, long noteDate) {\r
620                 if (dates.size()== 0) \r
621                         return true;\r
622                 \r
623                 boolean negative = false;\r
624                 for (int i=0; i<dates.size(); i++) {\r
625                         String requiredDate = dates.get(i);\r
626                         if (requiredDate.startsWith("-"))\r
627                                 negative = true;\r
628                         \r
629                         int response = 0;\r
630                         requiredDate = requiredDate.substring(requiredDate.indexOf(":")+1);\r
631                         try {\r
632                                 response = dateCheck(requiredDate, noteDate);\r
633                         } catch (java.lang.NumberFormatException e) {return false;}  {\r
634                                 if (negative && response < 0)\r
635                                         return false;\r
636                                 if (!negative && response > 0)\r
637                                         return false;\r
638                         }\r
639                 }\r
640                 return true;\r
641         }\r
642         private boolean matchDatesAny(List<String> dates, long noteDate) {\r
643                 if (dates.size()== 0) \r
644                         return true;\r
645                 \r
646                 boolean negative = false;\r
647                 for (int i=0; i<dates.size(); i++) {\r
648                         String requiredDate = dates.get(i);\r
649                         if (requiredDate.startsWith("-"))\r
650                                 negative = true;\r
651                         \r
652                         int response = 0;\r
653                         requiredDate = requiredDate.substring(requiredDate.indexOf(":")+1);\r
654                         try {\r
655                                 response = dateCheck(requiredDate, noteDate);\r
656                         } catch (java.lang.NumberFormatException e) {return false;}  {\r
657                                 if (negative && response > 0)\r
658                                         return true;\r
659                                 if (!negative && response < 0)\r
660                                         return true;\r
661                         }\r
662                 }\r
663                 return false;\r
664         }\r
665         \r
666         @SuppressWarnings("unused")\r
667         private void printCalendar(Calendar calendar) {\r
668                 // define output format and print\r
669                 SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy hh:mm:ss aaa");\r
670                 String date = sdf.format(calendar.getTime());\r
671                 System.err.print(date);\r
672                 calendar = new GregorianCalendar();\r
673         }\r
674         \r
675         \r
676         //****************************************\r
677         //****************************************\r
678         // Match search terms against notes\r
679         //****************************************\r
680         //****************************************\r
681         public List<Note> matchWords() {\r
682                 logger.log(logger.EXTREME, "Inside EnSearch.matchWords()");\r
683                 boolean subSelect = false;\r
684                 \r
685                 NoteTable noteTable = new NoteTable(logger, conn);  \r
686                 List<String> validGuids = new ArrayList<String>();\r
687                 \r
688                 if (searchWords.size() > 0) \r
689                         subSelect = true;\r
690 \r
691                 NSqlQuery query = new NSqlQuery(conn.getConnection());\r
692                 // Build a temp table for GUID results\r
693                 if (!conn.dbTableExists("SEARCH_RESULTS")) {\r
694                         query.exec("create temporary table SEARCH_RESULTS (guid varchar)");\r
695                         query.exec("create temporary table SEARCH_RESULTS_MERGE (guid varchar)");\r
696                 } else {\r
697                         query. exec("Delete from SEARCH_RESULTS");\r
698                         query. exec("Delete from SEARCH_RESULTS_MERGE");\r
699                 }\r
700 \r
701                 NSqlQuery insertQuery = new NSqlQuery(conn.getConnection());\r
702                 NSqlQuery indexQuery = new NSqlQuery(conn.getIndexConnection());\r
703                 NSqlQuery mergeQuery = new NSqlQuery(conn.getConnection());\r
704                 NSqlQuery deleteQuery = new NSqlQuery(conn.getConnection());\r
705                 \r
706                 insertQuery.prepare("Insert into SEARCH_RESULTS (guid) values (:guid)");\r
707                 mergeQuery.prepare("Insert into SEARCH_RESULTS_MERGE (guid) values (:guid)");\r
708                 \r
709                 if (subSelect) {\r
710                         for (int i=0; i<getWords().size(); i++) {\r
711                                 if (getWords().get(i).indexOf("*") == -1) {\r
712                                         indexQuery.prepare("Select distinct guid from words where weight >= " +minimumRecognitionWeight +\r
713                                                         " and word=:word");\r
714                                         indexQuery.bindValue(":word", getWords().get(i));\r
715                                 } else {\r
716                                         indexQuery.prepare("Select distinct guid from words where weight >= " +minimumRecognitionWeight +\r
717                                                 " and word like :word");\r
718                                         indexQuery.bindValue(":word", getWords().get(i).replace("*", "%"));\r
719                                 }\r
720                                 indexQuery.exec();\r
721                                 String guid = null;\r
722                                 while(indexQuery.next()) {\r
723                                         guid = indexQuery.valueString(0);\r
724                                         if (i==0 || any) {\r
725                                                 insertQuery.bindValue(":guid", guid);\r
726                                                 insertQuery.exec();\r
727                                         } else {\r
728                                                 mergeQuery.bindValue(":guid", guid);\r
729                                                 mergeQuery.exec();\r
730                                         }\r
731                                 }\r
732                                 if (i>0 && !any) {\r
733                                         deleteQuery.exec("Delete from SEARCH_RESULTS where guid not in (select guid from SEARCH_RESULTS_MERGE)");\r
734                                         deleteQuery.exec("Delete from SEARCH_RESULTS_MERGE");\r
735                                 }\r
736                         }\r
737 \r
738                         query.prepare("Select distinct guid from Note where guid in (Select guid from SEARCH_RESULTS)");\r
739                         if (!query.exec()) \r
740                                 logger.log(logger.LOW, "Error merging search results:" + query.lastError());\r
741                 \r
742                         while (query.next()) {\r
743                                 validGuids.add(query.valueString(0));\r
744                         }\r
745                 }\r
746                 \r
747                 List<Note> noteIndex = noteTable.getAllNotes();\r
748                 List<Note> guids = new ArrayList<Note>();\r
749                 for (int i=0; i<noteIndex.size(); i++) {\r
750                         Note n = noteIndex.get(i);\r
751                         boolean good = true;\r
752                         \r
753                         if (!validGuids.contains(n.getGuid()) && subSelect)\r
754                                 good = false;\r
755                                                 \r
756                         // Start matching special stuff, like tags & notebooks\r
757                         if (any) {\r
758                                 if (good && !matchTagsAny(n.getTagNames(), getTags()))\r
759                                         good = false;\r
760                                 if (good && !matchNotebook(n.getNotebookGuid()))\r
761                                         good = false;\r
762                                 if (good && !matchNotebookStack(n.getNotebookGuid()))\r
763                                         good = false;\r
764                                 if (good && !matchListAny(getIntitle(), n.getTitle()))\r
765                                         good = false;\r
766                                 if (good && !matchListAny(getAuthor(), n.getAttributes().getAuthor()))\r
767                                         good = false;\r
768                                 if (good && !matchListAny(getSource(), n.getAttributes().getSource()))\r
769                                         good = false;\r
770                                 if (good && !matchListAny(getSourceApplication(), n.getAttributes().getSourceApplication()))\r
771                                         good = false;\r
772                                 if (good && !matchContentAny(n))\r
773                                         good = false;\r
774                                 if (good && !matchDatesAny(getCreated(), n.getCreated()))\r
775                                         good = false;\r
776                                 if (good && !matchDatesAny(getUpdated(), n.getUpdated()))\r
777                                         good = false;\r
778                                 if (good && n.getAttributes() != null && !matchDatesAny(getSubjectDate(), n.getAttributes().getSubjectDate()))\r
779                                         good = false;\r
780                         } else {\r
781                                 if (good && !matchTagsAll(n.getTagNames(), getTags()))\r
782                                         good = false;\r
783                                 if (good && !matchNotebook(n.getNotebookGuid()))\r
784                                         good = false;\r
785                                 if (good && !matchNotebookStack(n.getNotebookGuid()))\r
786                                         good = false;\r
787                                 if (good && !matchListAll(getIntitle(), n.getTitle()))\r
788                                         good = false;\r
789                                 if (good && !matchListAll(getAuthor(), n.getAttributes().getAuthor()))\r
790                                         good = false;\r
791                                 if (good && !matchListAll(getSource(), n.getAttributes().getSource()))\r
792                                         good = false;\r
793                                 if (good && !matchListAll(getSourceApplication(), n.getAttributes().getSourceApplication()))\r
794                                         good = false;\r
795                                 if (good && !matchContentAll(n))\r
796                                         good = false;\r
797                                 if (good && !matchDatesAll(getCreated(), n.getCreated()))\r
798                                         good = false;\r
799                                 if (good && !matchDatesAll(getUpdated(), n.getUpdated()))\r
800                                         good = false;\r
801                                 if (good && n.getAttributes() != null && !matchDatesAll(getSubjectDate(), n.getAttributes().getSubjectDate()))\r
802                                         good = false;\r
803                         }\r
804                         if (good) {\r
805                                 guids.add(n);\r
806                         }\r
807                 }\r
808                 \r
809                 // For performance reasons, we didn't get the tags for every note individually.  We now need to \r
810                 // get them\r
811                 List<NoteTagsRecord> noteTags = noteTable.noteTagsTable.getAllNoteTags();\r
812                 for (int i=0; i<guids.size(); i++) {\r
813                         List<String> tags = new ArrayList<String>();\r
814                         List<String> names = new ArrayList<String>();\r
815                         for (int j=0; j<noteTags.size(); j++) {\r
816                                 if (guids.get(i).getGuid().equals(noteTags.get(j).noteGuid)) {\r
817                                         tags.add(noteTags.get(j).tagGuid);\r
818                                         names.add(getTagNameByGuid(noteTags.get(j).tagGuid));\r
819                                 }\r
820                         }\r
821                         \r
822                         guids.get(i).setTagGuids(tags);\r
823                         guids.get(i).setTagNames(names);\r
824                 };\r
825                 logger.log(logger.EXTREME, "Leaving EnSearch.matchWords()");\r
826                 return guids;\r
827         }\r
828         \r
829         \r
830         \r
831         private String getTagNameByGuid(String guid) {\r
832                 for (int i=0; i<tagIndex.size(); i++) {\r
833                         if (tagIndex.get(i).getGuid().equals(guid)) \r
834                                         return tagIndex.get(i).getName();\r
835                 }               \r
836                 return "";\r
837         }\r
838 \r
839         // Compare dates\r
840         public int dateCheck(String date, long noteDate)  throws java.lang.NumberFormatException  {\r
841                 int offset = 0;\r
842                 boolean found = false;\r
843                 GregorianCalendar calendar = new GregorianCalendar();\r
844                 \r
845                 if (date.contains("-")) {\r
846                         String modifier = date.substring(date.indexOf("-")+1);\r
847                         offset = new Integer(modifier);\r
848                         offset = 0-offset;\r
849                         date = date.substring(0,date.indexOf("-"));\r
850                 }\r
851                 \r
852                 if (date.contains("+")) {\r
853                         String modifier = date.substring(date.indexOf("+")+1);\r
854                         offset = new Integer(modifier);\r
855                         date = date.substring(0,date.indexOf("+"));\r
856                 }\r
857                 \r
858                 if (date.equalsIgnoreCase("today")) {\r
859                         calendar.add(Calendar.DATE, offset);\r
860                         calendar.set(Calendar.HOUR, 0);\r
861                         calendar.set(Calendar.MINUTE, 0);\r
862                         calendar.set(Calendar.SECOND, 1);\r
863                         found = true;\r
864                 }\r
865                 \r
866                 if (date.equalsIgnoreCase("month")) {\r
867                         calendar.add(Calendar.MONTH, offset);\r
868                         calendar.set(Calendar.DAY_OF_MONTH, 1);\r
869                         calendar.set(Calendar.HOUR, 0);\r
870                         calendar.set(Calendar.MINUTE, 0);\r
871                         calendar.set(Calendar.SECOND, 1);\r
872                         found = true;\r
873                 }\r
874 \r
875                 if (date.equalsIgnoreCase("year")) {\r
876                         calendar.add(Calendar.YEAR, offset);\r
877                         calendar.set(Calendar.MONTH, Calendar.JANUARY);\r
878                         calendar.set(Calendar.DAY_OF_MONTH, 1);\r
879                         calendar.set(Calendar.HOUR, 0);\r
880                         calendar.set(Calendar.MINUTE, 0);\r
881                         calendar.set(Calendar.SECOND, 1);\r
882                         found = true;\r
883                 }\r
884 \r
885                 if (date.equalsIgnoreCase("week")) {\r
886                         calendar.add(Calendar.DATE, 0-calendar.get(Calendar.DAY_OF_WEEK)+1);\r
887                         calendar.add(Calendar.DATE,(offset*7));\r
888                         calendar.set(Calendar.HOUR, 0);\r
889                         calendar.set(Calendar.MINUTE, 0);\r
890                         calendar.set(Calendar.SECOND, 1);\r
891 \r
892                         found = true;\r
893                 }\r
894                 \r
895                 // If nothing was found, then we have a date number\r
896                 if (!found) {\r
897                         calendar = stringToGregorianCalendar(date);\r
898                 }\r
899                 \r
900                 \r
901                 String dateTimeFormat = new String("yyyyMMdd-HHmmss");\r
902                 SimpleDateFormat simple = new SimpleDateFormat(dateTimeFormat);\r
903                 StringBuilder creationDate = new StringBuilder(simple.format(noteDate));\r
904                 GregorianCalendar nCalendar = stringToGregorianCalendar(creationDate.toString().replace("-", "T"));\r
905                 if (calendar == null || nCalendar == null)  // If we have something invalid, it automatically fails\r
906                         return 1;\r
907                 return calendar.compareTo(nCalendar);\r
908         }\r
909         private GregorianCalendar stringToGregorianCalendar(String date) {\r
910                 String datePart = date;\r
911                 GregorianCalendar calendar = new GregorianCalendar();\r
912                 boolean GMT = false;\r
913                 String timePart = "";\r
914                 if (date.contains("T")) {\r
915                         datePart = date.substring(0,date.indexOf("T"));\r
916                         timePart = date.substring(date.indexOf("T")+1);\r
917                 } else {\r
918                         timePart = "000001";\r
919                 }\r
920                 if (datePart.length() != 8)\r
921                         return null;\r
922                 calendar.set(Calendar.YEAR, new Integer(datePart.substring(0,4)));\r
923                 calendar.set(Calendar.MONTH, new Integer(datePart.substring(4,6))-1);\r
924                 calendar.set(Calendar.DAY_OF_MONTH, new Integer(datePart.substring(6)));\r
925                 if (timePart.endsWith("Z")) {\r
926                         GMT = true;\r
927                         timePart = timePart.substring(0,timePart.length()-1);\r
928                 }\r
929                 timePart = timePart.concat("000000");\r
930                 timePart = timePart.substring(0,6);\r
931                 calendar.set(Calendar.HOUR, new Integer(timePart.substring(0,2)));\r
932                 calendar.set(Calendar.MINUTE, new Integer(timePart.substring(2,4)));\r
933                 calendar.set(Calendar.SECOND, new Integer(timePart.substring(4)));\r
934                 if (GMT)\r
935                         calendar.set(Calendar.ZONE_OFFSET, -1*(calendar.get(Calendar.ZONE_OFFSET)/(1000*60*60)));\r
936                 return calendar;\r
937 \r
938         }\r
939                 \r
940 }\r