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 }