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