OSDN Git Service

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