001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.search;
016    
017    import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayInputStream;
018    import com.liferay.portal.kernel.search.geolocation.GeoLocationPoint;
019    import com.liferay.portal.kernel.util.ArrayUtil;
020    import com.liferay.portal.kernel.util.DateFormatFactoryUtil;
021    import com.liferay.portal.kernel.util.FastDateFormatFactoryUtil;
022    import com.liferay.portal.kernel.util.FileUtil;
023    import com.liferay.portal.kernel.util.GetterUtil;
024    import com.liferay.portal.kernel.util.LocaleUtil;
025    import com.liferay.portal.kernel.util.LocalizationUtil;
026    import com.liferay.portal.kernel.util.PropsKeys;
027    import com.liferay.portal.kernel.util.PropsUtil;
028    import com.liferay.portal.kernel.util.SetUtil;
029    import com.liferay.portal.kernel.util.StringBundler;
030    import com.liferay.portal.kernel.util.StringPool;
031    import com.liferay.portal.kernel.util.StringUtil;
032    import com.liferay.portal.kernel.util.Validator;
033    
034    import java.io.File;
035    import java.io.FileInputStream;
036    import java.io.IOException;
037    import java.io.InputStream;
038    
039    import java.math.BigDecimal;
040    
041    import java.text.DateFormat;
042    import java.text.Format;
043    import java.text.ParseException;
044    
045    import java.util.Arrays;
046    import java.util.Collection;
047    import java.util.Collections;
048    import java.util.Date;
049    import java.util.HashMap;
050    import java.util.Locale;
051    import java.util.Map;
052    import java.util.Set;
053    
054    /**
055     * @author Brian Wing Shun Chan
056     * @author Bruno Farache
057     */
058    public class DocumentImpl implements Document {
059    
060            public static String getLocalizedName(Locale locale, String name) {
061                    if (locale == null) {
062                            return name;
063                    }
064    
065                    String languageId = LocaleUtil.toLanguageId(locale);
066    
067                    return getLocalizedName(languageId, name);
068            }
069    
070            public static String getLocalizedName(String languageId, String name) {
071                    return LocalizationUtil.getLocalizedName(name, languageId);
072            }
073    
074            public static String getSortableFieldName(String name) {
075                    return name.concat(StringPool.UNDERLINE).concat(_SORTABLE_FIELD_SUFFIX);
076            }
077    
078            public static String getSortFieldName(Sort sort, String scoreFieldName) {
079                    if (sort.getType() == Sort.SCORE_TYPE) {
080                            return scoreFieldName;
081                    }
082    
083                    String fieldName = sort.getFieldName();
084    
085                    if (DocumentImpl.isSortableFieldName(fieldName)) {
086                            return fieldName;
087                    }
088    
089                    if ((sort.getType() == Sort.STRING_TYPE) &&
090                            !DocumentImpl.isSortableTextField(fieldName)) {
091    
092                            return scoreFieldName;
093                    }
094    
095                    return DocumentImpl.getSortableFieldName(fieldName);
096            }
097    
098            public static boolean isSortableFieldName(String name) {
099                    return name.endsWith(_SORTABLE_FIELD_SUFFIX);
100            }
101    
102            public static boolean isSortableTextField(String name) {
103                    return _defaultSortableTextFields.contains(name);
104            }
105    
106            @Override
107            public void add(Field field) {
108                    _fields.put(field.getName(), field);
109            }
110    
111            @Override
112            public void addDate(String name, Date value) {
113                    if (value == null) {
114                            return;
115                    }
116    
117                    addDate(name, new Date[] {value});
118            }
119    
120            @Override
121            public void addDate(String name, Date[] values) {
122                    if (values == null) {
123                            return;
124                    }
125    
126                    if (_dateFormat == null) {
127                            _dateFormat = FastDateFormatFactoryUtil.getSimpleDateFormat(
128                                    _INDEX_DATE_FORMAT_PATTERN);
129                    }
130    
131                    String[] dates = new String[values.length];
132                    Long[] datesTime = new Long[values.length];
133    
134                    for (int i = 0; i < values.length; i++) {
135                            dates[i] = _dateFormat.format(values[i]);
136                            datesTime[i] = values[i].getTime();
137                    }
138    
139                    createSortableNumericField(name, false, datesTime);
140    
141                    Field field = createField(name, dates);
142    
143                    field.setDates(values);
144            }
145    
146            @Override
147            public void addFile(String name, byte[] bytes, String fileExt) {
148                    InputStream is = new UnsyncByteArrayInputStream(bytes);
149    
150                    addFile(name, is, fileExt);
151            }
152    
153            @Override
154            public void addFile(String name, File file, String fileExt)
155                    throws IOException {
156    
157                    InputStream is = new FileInputStream(file);
158    
159                    addFile(name, is, fileExt);
160            }
161    
162            @Override
163            public void addFile(String name, InputStream is, String fileExt) {
164                    addText(name, FileUtil.extractText(is, fileExt));
165            }
166    
167            @Override
168            public void addFile(
169                    String name, InputStream is, String fileExt, int maxStringLength) {
170    
171                    addText(name, FileUtil.extractText(is, fileExt, maxStringLength));
172            }
173    
174            @Override
175            public void addGeoLocation(double latitude, double longitude) {
176                    Field field = new Field(Field.GEO_LOCATION);
177    
178                    field.setGeoLocationPoint(new GeoLocationPoint(latitude, longitude));
179    
180                    add(field);
181            }
182    
183            @Override
184            public void addKeyword(String name, boolean value) {
185                    addKeyword(name, String.valueOf(value));
186            }
187    
188            @Override
189            public void addKeyword(String name, Boolean value) {
190                    addKeyword(name, String.valueOf(value));
191            }
192    
193            @Override
194            public void addKeyword(String name, boolean[] values) {
195                    if (values == null) {
196                            return;
197                    }
198    
199                    addKeyword(name, ArrayUtil.toStringArray(values));
200            }
201    
202            @Override
203            public void addKeyword(String name, Boolean[] values) {
204                    if (values == null) {
205                            return;
206                    }
207    
208                    addKeyword(name, ArrayUtil.toStringArray(values));
209            }
210    
211            @Override
212            public void addKeyword(String name, double value) {
213                    addKeyword(name, String.valueOf(value));
214            }
215    
216            @Override
217            public void addKeyword(String name, Double value) {
218                    addKeyword(name, String.valueOf(value));
219            }
220    
221            @Override
222            public void addKeyword(String name, double[] values) {
223                    if (values == null) {
224                            return;
225                    }
226    
227                    addKeyword(name, ArrayUtil.toStringArray(values));
228            }
229    
230            @Override
231            public void addKeyword(String name, Double[] values) {
232                    if (values == null) {
233                            return;
234                    }
235    
236                    addKeyword(name, ArrayUtil.toStringArray(values));
237            }
238    
239            @Override
240            public void addKeyword(String name, float value) {
241                    addKeyword(name, String.valueOf(value));
242            }
243    
244            @Override
245            public void addKeyword(String name, Float value) {
246                    addKeyword(name, String.valueOf(value));
247            }
248    
249            @Override
250            public void addKeyword(String name, float[] values) {
251                    if (values == null) {
252                            return;
253                    }
254    
255                    addKeyword(name, ArrayUtil.toStringArray(values));
256            }
257    
258            @Override
259            public void addKeyword(String name, Float[] values) {
260                    if (values == null) {
261                            return;
262                    }
263    
264                    addKeyword(name, ArrayUtil.toStringArray(values));
265            }
266    
267            @Override
268            public void addKeyword(String name, int value) {
269                    addKeyword(name, String.valueOf(value));
270            }
271    
272            @Override
273            public void addKeyword(String name, int[] values) {
274                    if (values == null) {
275                            return;
276                    }
277    
278                    addKeyword(name, ArrayUtil.toStringArray(values));
279            }
280    
281            @Override
282            public void addKeyword(String name, Integer value) {
283                    addKeyword(name, String.valueOf(value));
284            }
285    
286            @Override
287            public void addKeyword(String name, Integer[] values) {
288                    if (values == null) {
289                            return;
290                    }
291    
292                    addKeyword(name, ArrayUtil.toStringArray(values));
293            }
294    
295            @Override
296            public void addKeyword(String name, long value) {
297                    addKeyword(name, String.valueOf(value));
298            }
299    
300            @Override
301            public void addKeyword(String name, Long value) {
302                    addKeyword(name, String.valueOf(value));
303            }
304    
305            @Override
306            public void addKeyword(String name, long[] values) {
307                    if (values == null) {
308                            return;
309                    }
310    
311                    addKeyword(name, ArrayUtil.toStringArray(values));
312            }
313    
314            @Override
315            public void addKeyword(String name, Long[] values) {
316                    if (values == null) {
317                            return;
318                    }
319    
320                    addKeyword(name, ArrayUtil.toStringArray(values));
321            }
322    
323            @Override
324            public void addKeyword(String name, short value) {
325                    addKeyword(name, String.valueOf(value));
326            }
327    
328            @Override
329            public void addKeyword(String name, Short value) {
330                    addKeyword(name, String.valueOf(value));
331            }
332    
333            @Override
334            public void addKeyword(String name, short[] values) {
335                    if (values == null) {
336                            return;
337                    }
338    
339                    addKeyword(name, ArrayUtil.toStringArray(values));
340            }
341    
342            @Override
343            public void addKeyword(String name, Short[] values) {
344                    if (values == null) {
345                            return;
346                    }
347    
348                    addKeyword(name, ArrayUtil.toStringArray(values));
349            }
350    
351            @Override
352            public void addKeyword(String name, String value) {
353                    addKeyword(name, value, false);
354            }
355    
356            @Override
357            public void addKeyword(String name, String value, boolean lowerCase) {
358                    createKeywordField(name, value, lowerCase);
359    
360                    createSortableKeywordField(name, value);
361            }
362    
363            @Override
364            public void addKeyword(String name, String[] values) {
365                    if (values == null) {
366                            return;
367                    }
368    
369                    createField(name, values);
370            }
371    
372            @Override
373            public void addKeywordSortable(String name, Boolean value) {
374                    String valueString = String.valueOf(value);
375    
376                    createKeywordField(name, valueString, false);
377    
378                    createSortableTextField(name, valueString);
379            }
380    
381            @Override
382            public void addKeywordSortable(String name, Boolean[] values) {
383                    if (values == null) {
384                            return;
385                    }
386    
387                    String[] valuesString = ArrayUtil.toStringArray(values);
388    
389                    createField(name, valuesString);
390    
391                    createSortableTextField(name, valuesString);
392            }
393    
394            @Override
395            public void addKeywordSortable(String name, String value) {
396                    createKeywordField(name, value, false);
397    
398                    createSortableTextField(name, value);
399            }
400    
401            @Override
402            public void addKeywordSortable(String name, String[] values) {
403                    createField(name, values);
404    
405                    createSortableTextField(name, values);
406            }
407    
408            @Override
409            public void addLocalizedKeyword(String name, Map<Locale, String> values) {
410                    addLocalizedKeyword(name, values, false);
411            }
412    
413            @Override
414            public void addLocalizedKeyword(
415                    String name, Map<Locale, String> values, boolean lowerCase) {
416    
417                    if ((values == null) || values.isEmpty()) {
418                            return;
419                    }
420    
421                    if (lowerCase) {
422                            Map<Locale, String> lowerCaseValues = new HashMap<>(values.size());
423    
424                            for (Map.Entry<Locale, String> entry : values.entrySet()) {
425                                    String value = GetterUtil.getString(entry.getValue());
426    
427                                    lowerCaseValues.put(
428                                            entry.getKey(), StringUtil.toLowerCase(value));
429                            }
430    
431                            values = lowerCaseValues;
432                    }
433    
434                    createField(name, values);
435            }
436    
437            @Override
438            public void addLocalizedKeyword(
439                    String name, Map<Locale, String> values, boolean lowerCase,
440                    boolean sortable) {
441    
442                    if ((values == null) || values.isEmpty()) {
443                            return;
444                    }
445    
446                    if (lowerCase) {
447                            Map<Locale, String> lowerCaseValues = new HashMap<>(values.size());
448    
449                            for (Map.Entry<Locale, String> entry : values.entrySet()) {
450                                    String value = GetterUtil.getString(entry.getValue());
451    
452                                    lowerCaseValues.put(
453                                            entry.getKey(), StringUtil.toLowerCase(value));
454                            }
455    
456                            values = lowerCaseValues;
457                    }
458    
459                    createField(name, values, sortable);
460            }
461    
462            @Override
463            public void addLocalizedText(String name, Map<Locale, String> values) {
464                    if ((values == null) || values.isEmpty()) {
465                            return;
466                    }
467    
468                    Field field = createField(name, values);
469    
470                    field.setTokenized(true);
471            }
472    
473            @Override
474            public void addNumber(String name, BigDecimal value) {
475                    createNumberField(name, value);
476            }
477    
478            @Override
479            public void addNumber(String name, BigDecimal[] values) {
480                    createNumberField(name, values);
481            }
482    
483            @Override
484            public void addNumber(String name, double value) {
485                    createNumberField(name, Double.valueOf(value));
486            }
487    
488            @Override
489            public void addNumber(String name, Double value) {
490                    createNumberField(name, value);
491            }
492    
493            @Override
494            public void addNumber(String name, double[] values) {
495                    if (values == null) {
496                            return;
497                    }
498    
499                    createNumberField(name, ArrayUtil.toArray(values));
500            }
501    
502            @Override
503            public void addNumber(String name, Double[] values) {
504                    createNumberField(name, values);
505            }
506    
507            @Override
508            public void addNumber(String name, float value) {
509                    createNumberField(name, Float.valueOf(value));
510            }
511    
512            @Override
513            public void addNumber(String name, Float value) {
514                    createNumberField(name, value);
515            }
516    
517            @Override
518            public void addNumber(String name, float[] values) {
519                    if (values == null) {
520                            return;
521                    }
522    
523                    createNumberField(name, ArrayUtil.toArray(values));
524            }
525    
526            @Override
527            public void addNumber(String name, Float[] values) {
528                    createNumberField(name, values);
529            }
530    
531            @Override
532            public void addNumber(String name, int value) {
533                    createNumberField(name, Integer.valueOf(value));
534            }
535    
536            @Override
537            public void addNumber(String name, int[] values) {
538                    if (values == null) {
539                            return;
540                    }
541    
542                    createNumberField(name, ArrayUtil.toArray(values));
543            }
544    
545            @Override
546            public void addNumber(String name, Integer value) {
547                    createNumberField(name, value);
548            }
549    
550            @Override
551            public void addNumber(String name, Integer[] values) {
552                    createNumberField(name, values);
553            }
554    
555            @Override
556            public void addNumber(String name, long value) {
557                    createNumberField(name, Long.valueOf(value));
558            }
559    
560            @Override
561            public void addNumber(String name, Long value) {
562                    createNumberField(name, value);
563            }
564    
565            @Override
566            public void addNumber(String name, long[] values) {
567                    if (values == null) {
568                            return;
569                    }
570    
571                    createNumberField(name, ArrayUtil.toArray(values));
572            }
573    
574            @Override
575            public void addNumber(String name, Long[] values) {
576                    createNumberField(name, values);
577            }
578    
579            @Override
580            public void addNumber(String name, String value) {
581                    createNumberField(name, Long.valueOf(value));
582            }
583    
584            @Override
585            public void addNumber(String name, String[] values) {
586                    if (values == null) {
587                            return;
588                    }
589    
590                    Long[] longs = new Long[values.length];
591    
592                    for (int i = 0; i < values.length; i++) {
593                            longs[i] = Long.valueOf(values[i]);
594                    }
595    
596                    createNumberField(name, longs);
597            }
598    
599            @Override
600            public void addNumberSortable(String name, BigDecimal value) {
601                    createNumberFieldWithTypedSortable(name, value);
602            }
603    
604            @Override
605            public void addNumberSortable(String name, BigDecimal[] values) {
606                    createNumberFieldWithTypedSortable(name, values);
607            }
608    
609            @Override
610            public void addNumberSortable(String name, Double value) {
611                    createNumberFieldWithTypedSortable(name, value);
612            }
613    
614            @Override
615            public void addNumberSortable(String name, Double[] values) {
616                    createNumberFieldWithTypedSortable(name, values);
617            }
618    
619            @Override
620            public void addNumberSortable(String name, Float value) {
621                    createNumberFieldWithTypedSortable(name, value);
622            }
623    
624            @Override
625            public void addNumberSortable(String name, Float[] values) {
626                    createNumberFieldWithTypedSortable(name, values);
627            }
628    
629            @Override
630            public void addNumberSortable(String name, Integer value) {
631                    createNumberFieldWithTypedSortable(name, value);
632            }
633    
634            @Override
635            public void addNumberSortable(String name, Integer[] values) {
636                    createNumberFieldWithTypedSortable(name, values);
637            }
638    
639            @Override
640            public void addNumberSortable(String name, Long value) {
641                    createNumberFieldWithTypedSortable(name, value);
642            }
643    
644            @Override
645            public void addNumberSortable(String name, Long[] values) {
646                    createNumberFieldWithTypedSortable(name, values);
647            }
648    
649            @Override
650            public void addText(String name, String value) {
651                    if (Validator.isNull(value)) {
652                            return;
653                    }
654    
655                    Field field = createField(name, value);
656    
657                    field.setTokenized(true);
658    
659                    createSortableKeywordField(name, value);
660            }
661    
662            @Override
663            public void addText(String name, String[] values) {
664                    if (values == null) {
665                            return;
666                    }
667    
668                    Field field = createField(name, values);
669    
670                    field.setTokenized(true);
671    
672                    createSortableKeywordField(name, values);
673            }
674    
675            @Override
676            public void addTextSortable(String name, String value) {
677                    if (Validator.isNull(value)) {
678                            return;
679                    }
680    
681                    Field field = createField(name, value);
682    
683                    field.setTokenized(true);
684    
685                    createSortableTextField(name, value);
686            }
687    
688            @Override
689            public void addTextSortable(String name, String[] values) {
690                    if (values == null) {
691                            return;
692                    }
693    
694                    Field field = createField(name, values);
695    
696                    field.setTokenized(true);
697    
698                    createSortableTextField(name, values);
699            }
700    
701            @Override
702            public void addUID(String portletId, long field1) {
703                    addUID(portletId, String.valueOf(field1));
704            }
705    
706            @Override
707            public void addUID(String portletId, long field1, String field2) {
708                    addUID(portletId, String.valueOf(field1), field2);
709            }
710    
711            @Override
712            public void addUID(String portletId, Long field1) {
713                    addUID(portletId, field1.longValue());
714            }
715    
716            @Override
717            public void addUID(String portletId, Long field1, String field2) {
718                    addUID(portletId, field1.longValue(), field2);
719            }
720    
721            @Override
722            public void addUID(String portletId, String field1) {
723                    addUID(portletId, field1, null);
724            }
725    
726            @Override
727            public void addUID(String portletId, String field1, String field2) {
728                    addUID(portletId, field1, field2, null);
729            }
730    
731            @Override
732            public void addUID(
733                    String portletId, String field1, String field2, String field3) {
734    
735                    addUID(portletId, field1, field2, field3, null);
736            }
737    
738            @Override
739            public void addUID(
740                    String portletId, String field1, String field2, String field3,
741                    String field4) {
742    
743                    String uid = portletId + _UID_PORTLET + field1;
744    
745                    if (field2 != null) {
746                            uid += _UID_FIELD + field2;
747                    }
748    
749                    if (field3 != null) {
750                            uid += _UID_FIELD + field3;
751                    }
752    
753                    if (field4 != null) {
754                            uid += _UID_FIELD + field4;
755                    }
756    
757                    addKeyword(Field.UID, uid);
758            }
759    
760            @Override
761            public Object clone() {
762                    DocumentImpl documentImpl = new DocumentImpl();
763    
764                    documentImpl.setSortableTextFields(_sortableTextFields);
765    
766                    return documentImpl;
767            }
768    
769            @Override
770            public String get(Locale locale, String name) {
771                    if (locale == null) {
772                            return get(name);
773                    }
774    
775                    String localizedName = getLocalizedName(locale, name);
776    
777                    Field field = getField(localizedName);
778    
779                    if (field == null) {
780                            field = getField(name);
781                    }
782    
783                    if (field == null) {
784                            return StringPool.BLANK;
785                    }
786    
787                    return field.getValue();
788            }
789    
790            @Override
791            public String get(Locale locale, String name, String defaultName) {
792                    if (locale == null) {
793                            return get(name, defaultName);
794                    }
795    
796                    String localizedName = getLocalizedName(locale, name);
797    
798                    Field field = getField(localizedName);
799    
800                    if (field == null) {
801                            localizedName = getLocalizedName(locale, defaultName);
802    
803                            field = getField(localizedName);
804                    }
805    
806                    if (field == null) {
807                            return StringPool.BLANK;
808                    }
809    
810                    return field.getValue();
811            }
812    
813            @Override
814            public String get(String name) {
815                    Field field = getField(name);
816    
817                    if (field == null) {
818                            return StringPool.BLANK;
819                    }
820    
821                    return field.getValue();
822            }
823    
824            @Override
825            public String get(String name, String defaultName) {
826                    Field field = getField(name);
827    
828                    if (field == null) {
829                            return get(defaultName);
830                    }
831    
832                    return field.getValue();
833            }
834    
835            @Override
836            public Date getDate(String name) throws ParseException {
837                    DateFormat dateFormat = DateFormatFactoryUtil.getSimpleDateFormat(
838                            _INDEX_DATE_FORMAT_PATTERN);
839    
840                    return dateFormat.parse(get(name));
841            }
842    
843            @Override
844            public Field getField(String name) {
845                    return doGetField(name, false);
846            }
847    
848            @Override
849            public Map<String, Field> getFields() {
850                    return _fields;
851            }
852    
853            @Override
854            public String getPortletId() {
855                    String uid = getUID();
856    
857                    int pos = uid.indexOf(_UID_PORTLET);
858    
859                    return uid.substring(0, pos);
860            }
861    
862            @Override
863            public String getUID() {
864                    Field field = getField(Field.UID);
865    
866                    if (field == null) {
867                            throw new RuntimeException("UID is not set");
868                    }
869    
870                    return field.getValue();
871            }
872    
873            @Override
874            public String[] getValues(String name) {
875                    Field field = getField(name);
876    
877                    if (field == null) {
878                            return new String[] {StringPool.BLANK};
879                    }
880    
881                    return field.getValues();
882            }
883    
884            @Override
885            public boolean hasField(String name) {
886                    if (_fields.containsKey(name)) {
887                            return true;
888                    }
889    
890                    return false;
891            }
892    
893            @Override
894            public boolean isDocumentSortableTextField(String name) {
895                    return _sortableTextFields.contains(name);
896            }
897    
898            @Override
899            public void remove(String name) {
900                    _fields.remove(name);
901            }
902    
903            public void setFields(Map<String, Field> fields) {
904                    _fields = fields;
905            }
906    
907            @Override
908            public void setSortableTextFields(String[] sortableTextFields) {
909                    _sortableTextFields = SetUtil.fromArray(sortableTextFields);
910            }
911    
912            @Override
913            public String toString() {
914                    StringBundler sb = new StringBundler(5 * _fields.size());
915    
916                    toString(sb, _fields.values());
917    
918                    return sb.toString();
919            }
920    
921            protected Field createField(String name) {
922                    return doGetField(name, true);
923            }
924    
925            protected Field createField(
926                    String name, boolean sortable, String... values) {
927    
928                    Field field = createField(name);
929    
930                    field.setSortable(sortable);
931                    field.setValues(values);
932    
933                    return field;
934            }
935    
936            protected Field createField(
937                    String name, Map<Locale, String> localizedValues) {
938    
939                    return createField(name, localizedValues, false);
940            }
941    
942            protected Field createField(
943                    String name, Map<Locale, String> localizedValues, boolean sortable) {
944    
945                    Field field = createField(name);
946    
947                    field.setLocalizedValues(localizedValues);
948                    field.setSortable(sortable);
949    
950                    return field;
951            }
952    
953            protected Field createField(String name, String... values) {
954                    return createField(name, false, values);
955            }
956    
957            protected void createKeywordField(
958                    String name, String value, boolean lowerCase) {
959    
960                    if (lowerCase && Validator.isNotNull(value)) {
961                            value = StringUtil.toLowerCase(value);
962                    }
963    
964                    createField(name, value);
965            }
966    
967            protected void createNumberField(
968                    String name, boolean typify, Number value) {
969    
970                    if (value == null) {
971                            return;
972                    }
973    
974                    String valueString = String.valueOf(value);
975    
976                    createSortableNumericField(name, typify, valueString, value.getClass());
977    
978                    createField(name, valueString);
979            }
980    
981            protected <T extends Number & Comparable<? super T>> void createNumberField(
982                    String name, boolean typify, T... values) {
983    
984                    if (values == null) {
985                            return;
986                    }
987    
988                    createSortableNumericField(name, typify, values);
989    
990                    createField(name, ArrayUtil.toStringArray(values));
991            }
992    
993            protected void createNumberField(String name, Number value) {
994                    createNumberField(name, false, value);
995            }
996    
997            protected <T extends Number & Comparable<? super T>> void createNumberField(
998                    String name, T... values) {
999    
1000                    createNumberField(name, false, values);
1001            }
1002    
1003            protected void createNumberFieldWithTypedSortable(
1004                    String name, Number value) {
1005    
1006                    createNumberField(name, true, value);
1007            }
1008    
1009            protected <T extends Number & Comparable<? super T>> void
1010                    createNumberFieldWithTypedSortable(String name, T... values) {
1011    
1012                    createNumberField(name, true, values);
1013            }
1014    
1015            protected void createSortableKeywordField(String name, String value) {
1016                    if (isDocumentSortableTextField(name)) {
1017                            createSortableTextField(name, value);
1018                    }
1019            }
1020    
1021            protected void createSortableKeywordField(String name, String[] values) {
1022                    if (isDocumentSortableTextField(name)) {
1023                            createSortableTextField(name, values);
1024                    }
1025            }
1026    
1027            protected void createSortableNumericField(
1028                    String name, boolean typify, String value,
1029                    Class<? extends Number> clazz) {
1030    
1031                    if (typify) {
1032                            name = name.concat(StringPool.UNDERLINE).concat("Number");
1033                    }
1034    
1035                    Field field = createField(getSortableFieldName(name), value);
1036    
1037                    field.setNumeric(true);
1038                    field.setNumericClass(clazz);
1039            }
1040    
1041            protected <T extends Number & Comparable<? super T>> void
1042                    createSortableNumericField(String name, boolean typify, T... values) {
1043    
1044                    if ((values == null) || (values.length == 0)) {
1045                            return;
1046                    }
1047    
1048                    T minValue = Collections.min(Arrays.asList(values));
1049    
1050                    createSortableNumericField(
1051                            name, typify, String.valueOf(minValue), minValue.getClass());
1052            }
1053    
1054            protected void createSortableTextField(String name, String value) {
1055                    String truncatedValue = value;
1056    
1057                    if (value.length() > _SORTABLE_TEXT_FIELDS_TRUNCATED_LENGTH) {
1058                            truncatedValue = value.substring(
1059                                    0, _SORTABLE_TEXT_FIELDS_TRUNCATED_LENGTH);
1060                    }
1061    
1062                    createKeywordField(getSortableFieldName(name), truncatedValue, true);
1063            }
1064    
1065            protected void createSortableTextField(String name, String[] values) {
1066                    if (values.length == 0) {
1067                            return;
1068                    }
1069    
1070                    createSortableTextField(name, Collections.min(Arrays.asList(values)));
1071            }
1072    
1073            protected Field doGetField(String name, boolean createIfNew) {
1074                    Field field = _fields.get(name);
1075    
1076                    if ((field == null) && createIfNew) {
1077                            field = new Field(name);
1078    
1079                            _fields.put(name, field);
1080                    }
1081    
1082                    return field;
1083            }
1084    
1085            protected void setSortableTextFields(Set<String> sortableTextFields) {
1086                    _sortableTextFields = sortableTextFields;
1087            }
1088    
1089            protected void toString(StringBundler sb, Collection<Field> fields) {
1090                    sb.append(StringPool.OPEN_CURLY_BRACE);
1091    
1092                    boolean firstField = true;
1093    
1094                    for (Field field : fields) {
1095                            if (!firstField) {
1096                                    sb.append(StringPool.COMMA);
1097                                    sb.append(StringPool.SPACE);
1098                            }
1099                            else {
1100                                    firstField = false;
1101                            }
1102    
1103                            if (field.hasChildren()) {
1104                                    sb.append(field.getName());
1105                                    sb.append(StringPool.COLON);
1106    
1107                                    toString(sb, field.getFields());
1108                            }
1109                            else {
1110                                    sb.append(field.getName());
1111                                    sb.append(StringPool.EQUAL);
1112                                    sb.append(Arrays.toString(field.getValues()));
1113                            }
1114                    }
1115    
1116                    sb.append(StringPool.CLOSE_CURLY_BRACE);
1117            }
1118    
1119            private static final String _INDEX_DATE_FORMAT_PATTERN = PropsUtil.get(
1120                    PropsKeys.INDEX_DATE_FORMAT_PATTERN);
1121    
1122            private static final String _SORTABLE_FIELD_SUFFIX = "sortable";
1123    
1124            private static final int _SORTABLE_TEXT_FIELDS_TRUNCATED_LENGTH =
1125                    GetterUtil.getInteger(
1126                            PropsUtil.get(
1127                                    PropsKeys.INDEX_SORTABLE_TEXT_FIELDS_TRUNCATED_LENGTH));
1128    
1129            private static final String _UID_FIELD = "_FIELD_";
1130    
1131            private static final String _UID_PORTLET = "_PORTLET_";
1132    
1133            private static Format _dateFormat;
1134            private static final Set<String> _defaultSortableTextFields =
1135                    SetUtil.fromArray(
1136                            PropsUtil.getArray(PropsKeys.INDEX_SORTABLE_TEXT_FIELDS));
1137    
1138            private Map<String, Field> _fields = new HashMap<>();
1139            private Set<String> _sortableTextFields = _defaultSortableTextFields;
1140    
1141    }