View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.util;
17  
18  import java.io.UnsupportedEncodingException;
19  import java.net.URLEncoder;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Date;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.codelibs.fess.Constants;
27  import org.codelibs.fess.crawler.util.CharUtil;
28  import org.codelibs.fess.taglib.FessFunctions;
29  import org.lastaflute.web.util.LaRequestUtil;
30  
31  import jakarta.servlet.http.HttpServletRequest;
32  
33  /**
34   * Utility class for document data manipulation and type conversion.
35   * This class provides static methods for extracting typed values from document maps,
36   * URL encoding, and other document-related operations. It's designed as a final
37   * utility class with only static methods.
38   *
39   */
40  public final class DocumentUtil {
41  
42      /**
43       * Private constructor to prevent instantiation of this utility class.
44       */
45      private DocumentUtil() {
46          // Utility class - no instantiation
47      }
48  
49      /**
50       * Gets a typed value from a document map with a default value.
51       *
52       * @param <T> the type to convert the value to
53       * @param doc the document map to extract the value from
54       * @param key the key to look up in the document map
55       * @param clazz the class type to convert the value to
56       * @param defaultValue the default value to return if the key is not found or conversion fails
57       * @return the converted value or the default value if not found
58       */
59      public static <T> T getValue(final Map<String, Object> doc, final String key, final Class<T> clazz, final T defaultValue) {
60          final T value = getValue(doc, key, clazz);
61          if (value == null) {
62              return defaultValue;
63          }
64          return value;
65      }
66  
67      /**
68       * Gets a typed value from a document map.
69       * Supports conversion to String, Date, Long, Integer, Double, Float, Boolean,
70       * List, and String array types. Handles both single values and arrays/lists.
71       *
72       * @param <T> the type to convert the value to
73       * @param doc the document map to extract the value from
74       * @param key the key to look up in the document map
75       * @param clazz the class type to convert the value to
76       * @return the converted value or null if not found or conversion fails
77       */
78      @SuppressWarnings("unchecked")
79      public static <T> T getValue(final Map<String, Object> doc, final String key, final Class<T> clazz) {
80          if (doc == null || key == null) {
81              return null;
82          }
83  
84          final Object value = doc.get(key);
85          if (value == null) {
86              return null;
87          }
88  
89          if (value instanceof List) {
90              if (clazz.isAssignableFrom(List.class)) {
91                  return (T) value;
92              }
93              if (clazz.isAssignableFrom(String[].class)) {
94                  return (T) ((List<?>) value).stream().filter(s -> s != null).map(Object::toString).toArray(n -> new String[n]);
95              }
96  
97              if (((List<?>) value).isEmpty()) {
98                  return null;
99              }
100 
101             return convertObj(((List<?>) value).get(0), clazz);
102         }
103         if (value instanceof String[]) {
104             if (clazz.isAssignableFrom(String[].class)) {
105                 return (T) value;
106             }
107             if (clazz.isAssignableFrom(List.class)) {
108                 final List<String> list = new ArrayList<>();
109                 Collections.addAll(list, (String[]) value);
110                 return (T) list;
111             }
112 
113             if (((String[]) value).length == 0) {
114                 return null;
115             }
116 
117             return convertObj(((String[]) value)[0], clazz);
118         }
119 
120         return convertObj(value, clazz);
121     }
122 
123     /**
124      * Converts an object to the specified type.
125      * Supports conversion to String, Date, Long, Integer, Double, Float, and Boolean types.
126      *
127      * @param <T> the type to convert the value to
128      * @param value the value to convert
129      * @param clazz the target class type
130      * @return the converted value or null if conversion is not supported
131      */
132     @SuppressWarnings("unchecked")
133     private static <T> T convertObj(final Object value, final Class<T> clazz) {
134         if (value == null) {
135             return null;
136         }
137 
138         if (clazz.isAssignableFrom(String.class)) {
139             return (T) value.toString();
140         }
141         if (clazz.isAssignableFrom(Date.class)) {
142             if (value instanceof Date) {
143                 return (T) value;
144             }
145             return (T) FessFunctions.parseDate(value.toString());
146         }
147         if (clazz.isAssignableFrom(Long.class)) {
148             if (value instanceof Long) {
149                 return (T) value;
150             }
151             return (T) Long.valueOf(value.toString());
152         }
153         if (clazz.isAssignableFrom(Integer.class)) {
154             if (value instanceof Integer) {
155                 return (T) value;
156             }
157             return (T) Integer.valueOf(value.toString());
158         }
159         if (clazz.isAssignableFrom(Double.class)) {
160             if (value instanceof Double) {
161                 return (T) value;
162             }
163             return (T) Double.valueOf(value.toString());
164         }
165         if (clazz.isAssignableFrom(Float.class)) {
166             if (value instanceof Float) {
167                 return (T) value;
168             }
169             return (T) Float.valueOf(value.toString());
170         }
171         if (clazz.isAssignableFrom(Boolean.class)) {
172             if (value instanceof Boolean) {
173                 return (T) value;
174             }
175             return (T) Boolean.valueOf(value.toString());
176         }
177         return null;
178     }
179 
180     /**
181      * Encodes a URL by encoding non-URL-safe characters.
182      * Uses the request's character encoding if available, otherwise defaults to UTF-8.
183      * Only encodes characters that are not considered URL-safe according to CharUtil.
184      *
185      * @param url the URL to encode
186      * @return the encoded URL with non-URL-safe characters properly encoded
187      */
188     public static String encodeUrl(final String url) {
189         final String enc = LaRequestUtil.getOptionalRequest()
190                 .filter(req -> req.getCharacterEncoding() != null)
191                 .map(HttpServletRequest::getCharacterEncoding)
192                 .orElse(Constants.UTF_8);
193         final StringBuilder buf = new StringBuilder(url.length() + 100);
194         for (final char c : url.toCharArray()) {
195             if (CharUtil.isUrlChar(c)) {
196                 buf.append(c);
197             } else {
198                 try {
199                     buf.append(URLEncoder.encode(String.valueOf(c), enc));
200                 } catch (final UnsupportedEncodingException e) {
201                     buf.append(c);
202                 }
203             }
204         }
205         return buf.toString();
206     }
207 }