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