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