001package com.mrivanplays.annotationconfig.yaml; 002 003import com.mrivanplays.annotationconfig.core.resolver.MultilineString; 004import com.mrivanplays.annotationconfig.core.resolver.ValueWriter; 005import com.mrivanplays.annotationconfig.core.resolver.settings.Settings; 006import com.mrivanplays.annotationconfig.core.utils.ReflectionUtils; 007import java.io.PrintWriter; 008import java.util.Arrays; 009import java.util.Collections; 010import java.util.LinkedList; 011import java.util.List; 012import java.util.Map; 013 014/** 015 * Represents the default yaml value writer. It uses homebrew writing to support comments. 016 * 017 * @author MrIvanPlays 018 * @since 2.1.0 019 */ 020@SuppressWarnings("unchecked") 021public final class YamlValueWriter implements ValueWriter { 022 023 @Override 024 public void write( 025 Map<String, Object> values, 026 Map<String, List<String>> fieldComments, 027 PrintWriter writer, 028 Settings settings) { 029 for (Map.Entry<String, Object> entry : values.entrySet()) { 030 write(null, entry.getKey(), entry.getValue(), writer, fieldComments, 2, false, false); 031 } 032 } 033 034 private void write( 035 String parentKey, 036 String key, 037 Object value, 038 PrintWriter writer, 039 Map<String, List<String>> commentsMap, 040 int childIndents, 041 boolean child, 042 boolean additional2Spaces) { 043 StringBuilder intentPrefixBuilder = new StringBuilder(); 044 for (int i = 0; i < childIndents; i++) { 045 intentPrefixBuilder.append(" "); 046 } 047 String intentPrefix = intentPrefixBuilder.toString(); 048 String parentSpec = parentKey != null ? parentKey.concat("." + key) : key; 049 if (value instanceof Map<?, ?>) { 050 String firstCheck = parentKey != null ? parentKey + "." + key : key; 051 List<String> baseComments = commentsMap.getOrDefault(firstCheck, Collections.emptyList()); 052 if (baseComments.isEmpty() && !firstCheck.equalsIgnoreCase(key)) { 053 baseComments = commentsMap.getOrDefault(key, Collections.emptyList()); 054 } 055 String childPrefix = child ? intentPrefix.substring(0, childIndents - 2) : ""; 056 if (!baseComments.isEmpty()) { 057 for (String comment : baseComments) { 058 writer.println(childPrefix + "# " + comment); 059 } 060 } 061 writer.println((additional2Spaces ? intentPrefix : childPrefix) + key + ":"); 062 for (Map.Entry<String, Object> entry : ((Map<String, Object>) value).entrySet()) { 063 String mapKey = entry.getKey(); 064 Object v = entry.getValue(); 065 if (v instanceof List<?>) { 066 writeCommentsInsideMap(parentKey, key, writer, commentsMap, intentPrefix, mapKey); 067 List<?> vList = (List<?>) v; 068 if (vList.isEmpty()) { 069 writer.println(intentPrefix + mapKey + ": []"); 070 } else { 071 boolean hasMapInsideMap = false; 072 OUT: 073 for (Object b : vList) { 074 if (b instanceof Map) { 075 Map<String, Object> map = (Map<String, Object>) b; 076 for (Map.Entry<String, Object> e1 : map.entrySet()) { 077 if (e1.getValue() instanceof Map) { 078 hasMapInsideMap = true; 079 break OUT; 080 } 081 } 082 } 083 } 084 if (!hasMapInsideMap) { 085 writer.println(intentPrefix + mapKey + ":"); 086 } 087 for (Object b : vList) { 088 if (!(b instanceof String)) { 089 if (b instanceof Map) { 090 Map<String, Object> map = (Map<String, Object>) b; 091 boolean firstValue = true; 092 List<Map.Entry<String, Object>> reservedObjectsForLater = new LinkedList<>(); 093 for (Map.Entry<String, Object> e : map.entrySet()) { 094 if (e.getValue() instanceof Map || e.getValue() instanceof List) { 095 reservedObjectsForLater.add(e); 096 continue; 097 } 098 StringBuilder valueAppend = new StringBuilder(); 099 if (e.getValue() instanceof String) { 100 valueAppend.append('"').append(e.getValue()).append('"'); 101 } else { 102 valueAppend.append(e.getValue()); 103 } 104 if (firstValue) { 105 writer.println(intentPrefix + "- " + e.getKey() + ": " + valueAppend); 106 firstValue = false; 107 } else { 108 writer.println(intentPrefix + " " + e.getKey() + ": " + valueAppend); 109 } 110 } 111 for (Map.Entry<String, Object> reserved : reservedObjectsForLater) { 112 write( 113 parentSpec, 114 reserved.getKey(), 115 reserved.getValue(), 116 writer, 117 commentsMap, 118 childIndents + 2, 119 true, 120 true); 121 } 122 } else { 123 writer.println(intentPrefix + " - " + b); 124 } 125 } else { 126 writer.println(intentPrefix + " - \"" + b + "\""); 127 } 128 } 129 } 130 } else if (v instanceof Map<?, ?>) { 131 write( 132 parentSpec, 133 mapKey, 134 v, 135 writer, 136 commentsMap, 137 childIndents + 2, 138 true, 139 additional2Spaces); 140 } else { 141 writeCommentsInsideMap(parentKey, key, writer, commentsMap, intentPrefix, mapKey); 142 if (!(v instanceof String)) { 143 if (v instanceof MultilineString) { 144 MultilineString multiline = (MultilineString) v; 145 String toWrite = multiline.getString(); 146 char c = multiline.getMarkerChar(); 147 if (toWrite.indexOf('\n') != -1) { 148 String[] parts = toWrite.split("\n"); 149 if (c == '|' || c == '>') { 150 writer.println(intentPrefix + mapKey + ": " + c); 151 } else if (c != '"') { 152 throw new IllegalArgumentException( 153 "Invalid multiline string character '" + c + "' for YAML"); 154 } else { 155 writer.println(intentPrefix + mapKey + ": \""); 156 } 157 for (int i = 0; i < parts.length; i++) { 158 String part = parts[i]; 159 if ((i + 1) == parts.length && c == '"') { 160 writer.println(intentPrefix + part + "\""); 161 } else { 162 writer.println(intentPrefix + part + "\\n"); 163 } 164 } 165 } else { 166 writer.println( 167 intentPrefix 168 + (additional2Spaces ? " " : "") 169 + mapKey 170 + ": \"" 171 + toWrite 172 + "\""); 173 } 174 } else { 175 if (v.getClass().isArray()) { 176 writer.println( 177 intentPrefix 178 + (additional2Spaces ? " " : "") 179 + mapKey 180 + ": " 181 + Arrays.toString(ReflectionUtils.castToArray(v))); 182 } else { 183 writer.println(intentPrefix + (additional2Spaces ? " " : "") + mapKey + ": " + v); 184 } 185 } 186 } else { 187 writer.println( 188 intentPrefix + (additional2Spaces ? " " : "") + mapKey + ": \"" + v + "\""); 189 } 190 } 191 } 192 } else if (value instanceof List<?>) { 193 writeComments(key, writer, commentsMap); 194 List<?> valueList = (List<?>) value; 195 if (valueList.isEmpty()) { 196 writer.println((child ? intentPrefix : "") + key + ": []"); 197 } else { 198 boolean hasMapInsideMap = false; 199 OUT: 200 for (Object b : valueList) { 201 if (b instanceof Map) { 202 Map<String, Object> map = (Map<String, Object>) b; 203 for (Map.Entry<String, Object> e1 : map.entrySet()) { 204 if (e1.getValue() instanceof Map) { 205 hasMapInsideMap = true; 206 break OUT; 207 } 208 } 209 } 210 } 211 if (!hasMapInsideMap) { 212 writer.println((child ? intentPrefix : "") + key + ":"); 213 } 214 for (Object b : valueList) { 215 if (!(b instanceof String)) { 216 if (b instanceof Map) { 217 Map<String, Object> map = (Map<String, Object>) b; 218 boolean firstValue = true; 219 List<Map.Entry<String, Object>> reservedObjectsForLater = new LinkedList<>(); 220 for (Map.Entry<String, Object> e : map.entrySet()) { 221 if (e.getValue() instanceof Map || e.getValue() instanceof List) { 222 reservedObjectsForLater.add(e); 223 continue; 224 } 225 StringBuilder valueAppend = new StringBuilder(); 226 if (e.getValue() instanceof String) { 227 valueAppend.append('"').append(e.getValue()).append('"'); 228 } else { 229 valueAppend.append(e.getValue()); 230 } 231 if (firstValue) { 232 writer.println(intentPrefix + "- " + e.getKey() + ": " + valueAppend); 233 firstValue = false; 234 } else { 235 writer.println(intentPrefix + " " + e.getKey() + ": " + valueAppend); 236 } 237 } 238 for (Map.Entry<String, Object> reserved : reservedObjectsForLater) { 239 write( 240 parentSpec, 241 reserved.getKey(), 242 reserved.getValue(), 243 writer, 244 commentsMap, 245 childIndents + 2, 246 true, 247 true); 248 } 249 } else { 250 writer.println(intentPrefix + "- " + b); 251 } 252 } else { 253 writer.println(intentPrefix + "- \"" + b + "\""); 254 } 255 } 256 } 257 } else { 258 writeComments(key, writer, commentsMap); 259 if (!(value instanceof String)) { 260 if (value instanceof MultilineString) { 261 MultilineString multiline = (MultilineString) value; 262 String toWrite = multiline.getString(); 263 char c = multiline.getMarkerChar(); 264 if (toWrite.indexOf('\n') != -1) { 265 String[] parts = toWrite.split("\n"); 266 if (c == '|' || c == '>') { 267 writer.println((child ? intentPrefix : "") + key + ": " + c); 268 } else if (c != '"') { 269 throw new IllegalArgumentException( 270 "Invalid multiline string character '" + c + "' for YAML"); 271 } else { 272 writer.println((child ? intentPrefix : "") + key + ": \""); 273 } 274 for (int i = 0; i < parts.length; i++) { 275 String part = parts[i]; 276 if ((i + 1) == parts.length && c == '"') { 277 writer.println((child ? intentPrefix : "") + part + "\""); 278 } else { 279 writer.println((child ? intentPrefix : "") + part + "\\n"); 280 } 281 } 282 } else { 283 writer.println((child ? intentPrefix : "") + key + ": \"" + toWrite + "\""); 284 } 285 } else { 286 if (value.getClass().isArray()) { 287 writer.println( 288 (child ? intentPrefix : "") 289 + key 290 + ": " 291 + Arrays.toString(ReflectionUtils.castToArray(value))); 292 } else { 293 writer.println((child ? intentPrefix : "") + key + ": " + value); 294 } 295 } 296 } else { 297 writer.println((child ? intentPrefix : "") + key + ": \"" + value + "\""); 298 } 299 } 300 if (!child) { 301 writer.append('\n'); 302 } 303 } 304 305 private void writeComments( 306 String key, PrintWriter writer, Map<String, List<String>> commentsMap) { 307 List<String> comments = commentsMap.getOrDefault(key, Collections.emptyList()); 308 if (!comments.isEmpty()) { 309 for (String comment : comments) { 310 writer.println("# " + comment); 311 } 312 } 313 } 314 315 private void writeCommentsInsideMap( 316 String parentKey, 317 String key, 318 PrintWriter writer, 319 Map<String, List<String>> commentsMap, 320 String intentPrefix, 321 String mapKey) { 322 List<String> comments = 323 commentsMap.getOrDefault( 324 (parentKey != null ? parentKey + "." : "") + key + "." + mapKey, 325 Collections.emptyList()); 326 if (!comments.isEmpty()) { 327 for (String comment : comments) { 328 writer.println(intentPrefix + "# " + comment); 329 } 330 } 331 } 332}