001/* 002 * **** BEGIN LICENSE BLOCK ***** 003 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 004 * 005 * The contents of this file are subject to the Mozilla Public License Version 006 * 1.1 (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * http://www.mozilla.org/MPL/ 009 * 010 * Software distributed under the License is distributed on an "AS IS" basis, 011 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 012 * for the specific language governing rights and limitations under the 013 * License. 014 * 015 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 016 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 017 * 018 * The Initial Developer of the Original Code is 019 * Agfa Healthcare. 020 * Portions created by the Initial Developer are Copyright (C) 2014 021 * the Initial Developer. All Rights Reserved. 022 * 023 * Contributor(s): 024 * See @authors listed below 025 * 026 * Alternatively, the contents of this file may be used under the terms of 027 * either the GNU General Public License Version 2 or later (the "GPL"), or 028 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 029 * in which case the provisions of the GPL or the LGPL are applicable instead 030 * of those above. If you wish to allow use of your version of this file only 031 * under the terms of either the GPL or the LGPL, and not to allow others to 032 * use your version of this file under the terms of the MPL, indicate your 033 * decision by deleting the provisions above and replace them with the notice 034 * and other provisions required by the GPL or the LGPL. If you do not delete 035 * the provisions above, a recipient may use your version of this file under 036 * the terms of any one of the MPL, the GPL or the LGPL. 037 * 038 * ***** END LICENSE BLOCK ***** 039 */ 040package org.dcm4che3.conf.core.util; 041 042import org.dcm4che3.conf.core.Nodes; 043import org.dcm4che3.conf.core.api.Configuration; 044import org.dcm4che3.conf.core.api.ConfigurationException; 045import org.dcm4che3.conf.core.api.internal.ConfigProperty; 046import org.dcm4che3.conf.core.api.internal.ConfigReflection; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050import java.util.*; 051import java.util.Map.Entry; 052 053@SuppressWarnings("unchecked") 054public class ConfigNodeTraverser { 055 056 private static Logger log = LoggerFactory.getLogger(ConfigNodeTraverser.class); 057 058 public interface ConfigNodeTypesafeFilter { 059 /** 060 * @return if returns true, traversal will skip going inside this node 061 */ 062 boolean beforeNode(Map<String, Object> containerNode, Class containerNodeClass, ConfigProperty property) throws ConfigurationException; 063 } 064 065 public interface ConfigNodesTypesafeFilter { 066 /** 067 * --TODO Is skipping node indicated by returning true or throwing exception? 068 * @return if returns true, traversal will skip going inside this node 069 */ 070 void beforeNodes(Map<String, Object> containerNode1, Map<String, Object> containerNode2, Class containerNodeClass, ConfigProperty property) throws ConfigurationException; 071 } 072 073 public static class AConfigNodeFilter { 074 public void beforeNodeElement(Map<String, Object> containerNode, String key, Object value) { 075 } 076 077 public void afterNodeElement(Map<String, Object> containerNode, String key, Object value) { 078 } 079 080 public void beforeNode(Map<String, Object> node) { 081 } 082 083 public void afterNode(Map<String, Object> node) { 084 } 085 086 public void beforeList(Collection list) { 087 } 088 089 public void beforeListElement(Collection list, int index, Object element) { 090 } 091 092 public void afterListElement(Collection list, int index, Object element) { 093 } 094 095 public void afterList(Collection list) { 096 } 097 098 /** 099 * Fired for Boolean,String,Number,null 100 */ 101 public void onPrimitiveNodeElement(Map<String, Object> containerNode, String key, Object value) { 102 } 103 104 /** 105 * Fired for Boolean,String,Number,null 106 */ 107 public void onPrimitiveListElement(Collection list, Object element) { 108 } 109 } 110 111 public static class ADualNodeFilter { 112 public void beforeNode(Map<String, Object> node1, Map<String, Object> node2) { 113 114 } 115 116 public void afterNode(Map<String, Object> node1, Map<String, Object> node2) { 117 118 } 119 120 public void afterNodeProperty(String key) { 121 122 } 123 124 public void beforeNodeProperty(String key) { 125 126 } 127 128 public void beforeListElement(int index1, int index2) { 129 130 } 131 132 public void afterListElement(int index1, int index2) { 133 134 } 135 136 /** 137 * @param node1 138 * @param node2 139 * @return false if the traverser should skip this list, true if it should proceed normally 140 */ 141 public boolean beforeList(List node1, List node2) { 142 return true; 143 } 144 145 public void afterList(List node1, List node2) { 146 147 } 148 } 149 150 public static void traverseNodeTypesafe(Object node, ConfigProperty containerProperty, List<Class> allExtensionClasses, ConfigNodeTypesafeFilter filter) throws ConfigurationException { 151 152 // if because of any reason this is not a map (e.g. a reference or a custom adapter for a configurableclass), 153 // we don't go deeper 154 if (!(node instanceof Map)) return; 155 156 // if that's a reference, don't traverse deeper 157 if (containerProperty.isReference()) return; 158 159 Map<String, Object> containerNode = (Map<String, Object>) node; 160 161 162 List<ConfigProperty> properties = ConfigReflection.getAllConfigurableFields(containerProperty.getRawClass()); 163 for (ConfigProperty property : properties) { 164 Object childNode = containerNode.get(property.getAnnotatedName()); 165 166 if (filter.beforeNode(containerNode, containerProperty.getRawClass(), property)) continue; 167 168 if (childNode == null) continue; 169 170 // if the property is a configclass 171 if (property.isConfObject()) { 172 traverseNodeTypesafe(childNode, property, allExtensionClasses, filter); 173 continue; 174 } 175 176 // collection, where a generics parameter is a configurable class or it is an array with comp type of configurableClass 177 if (property.isCollectionOfConfObjects() || property.isArrayOfConfObjects()) { 178 179 Collection collection = (Collection) childNode; 180 181 for (Object object : collection) { 182 traverseNodeTypesafe(object, property.getPseudoPropertyForConfigClassCollectionElement(), allExtensionClasses, filter); 183 } 184 185 continue; 186 } 187 188 // map, where a value generics parameter is a configurable class 189 if (property.isMapOfConfObjects()) { 190 191 try { 192 Map<String, Object> collection = (Map<String, Object>) childNode; 193 194 for (Object object : collection.values()) 195 traverseNodeTypesafe(object, property.getPseudoPropertyForConfigClassCollectionElement(), allExtensionClasses, filter); 196 197 } catch (ClassCastException e) { 198 log.warn("Map is malformed", e); 199 } 200 201 continue; 202 } 203 204 // extensions map 205 if (property.isExtensionsProperty()) { 206 207 try { 208 Map<String, Object> extensionsMap = (Map<String, Object>) childNode; 209 210 for (Entry<String, Object> entry : extensionsMap.entrySet()) { 211 try { 212 traverseNodeTypesafe( 213 entry.getValue(), 214 ConfigReflection.getDummyPropertyForClass(Extensions.getExtensionClassBySimpleName(entry.getKey(), allExtensionClasses)), 215 allExtensionClasses, 216 filter 217 ); 218 219 } catch (ClassNotFoundException e) { 220 // noop 221 log.debug("Extension class {} not found, parent node class {} ", entry.getKey(), containerProperty.getRawClass().getName()); 222 } 223 } 224 225 } catch (ClassCastException e) { 226 log.warn("Extensions are malformed", e); 227 } 228 229 } 230 231 232 } 233 } 234 235 public static void dualTraverseNodeTypesafe(Object node1, Object node2, ConfigProperty containerProperty, List<Class> allExtensionClasses, ConfigNodesTypesafeFilter filter) throws ConfigurationException { 236 237 // if because of any reason these are not maps (e.g. a reference or a custom adapter for a configurableclass), 238 // we don't go deeper 239 if (!(node1 instanceof Map) || !(node2 instanceof Map)) return; 240 241 // if that's a reference, don't traverse deeper 242 if (containerProperty.isReference()) return; 243 244 Map<String, Object> containerNode1 = (Map<String, Object>) node1; 245 Map<String, Object> containerNode2 = (Map<String, Object>) node2; 246 247 248 List<ConfigProperty> properties = ConfigReflection.getAllConfigurableFields(containerProperty.getRawClass()); 249 for (ConfigProperty property : properties) { 250 251 filter.beforeNodes(containerNode1, containerNode2, containerProperty.getRawClass(), property); 252 253 Object childNode1 = containerNode1.get(property.getAnnotatedName()); 254 Object childNode2 = containerNode2.get(property.getAnnotatedName()); 255 256 if (childNode1 == null || childNode2 == null) continue; 257 258 // if the property is a configclass 259 if (property.isConfObject()) { 260 dualTraverseNodeTypesafe(childNode1, childNode2, property, allExtensionClasses, filter); 261 continue; 262 } 263 264 // collection, where a generics parameter is a configurable class or it is an array with comp type of configurableClass 265 if (property.isCollectionOfConfObjects() || property.isArrayOfConfObjects()) { 266 267 List<Map<String, Object>> nodeList1 = (List) childNode1; 268 List<Map<String, Object>> nodeList2 = (List) childNode2; 269 270 boolean allUuids = allElementsAreNodesAndHaveUuids(nodeList1) 271 && allElementsAreNodesAndHaveUuids(nodeList2); 272 273 if (allUuids) { 274 // match on #uuid 275 // extract Map uuid -> index 276 Map<String, Integer> index1 = new HashMap<String, Integer>(); 277 278 int i = 0; 279 for (Map<String, Object> node : nodeList1) { 280 index1.put((String) node.get(Configuration.UUID_KEY), i); 281 i++; 282 } 283 284 for (Map<String, Object> node : nodeList2) { 285 286 Object uuid = node.get(Configuration.UUID_KEY); 287 Integer elem1Ind = index1.get(uuid); 288 289 if (elem1Ind != null) { 290 // found match 291 dualTraverseNodeTypesafe( 292 nodeList1.get(elem1Ind), 293 node, 294 property.getPseudoPropertyForConfigClassCollectionElement(), 295 allExtensionClasses, 296 filter 297 ); 298 } 299 } 300 } 301 continue; 302 } 303 304 // map, where a value generics parameter is a configurable class 305 if (property.isMapOfConfObjects()) { 306 307 308 Map<String, Map<String, Object>> mapNode1 = (Map) childNode1; 309 Map<String, Map<String, Object>> mapNode2 = (Map) childNode2; 310 311 if (allEntriesAreNodesAndHaveUuids(mapNode1) && 312 allEntriesAreNodesAndHaveUuids(mapNode2)) { 313 // conf objects with uuids 314 315 // match on #uuid 316 // extract Map uuid -> index 317 Map<String, String> index1 = new HashMap<String, String>(); 318 for (Entry<String, Map<String, Object>> stringMapEntry : mapNode1.entrySet()) { 319 index1.put( 320 (String) stringMapEntry.getValue().get(Configuration.UUID_KEY), 321 stringMapEntry.getKey() 322 ); 323 } 324 325 for (Entry<String, Map<String, Object>> stringMapEntry : mapNode2.entrySet()) { 326 Object uuidInSecondMapValue = stringMapEntry.getValue().get(Configuration.UUID_KEY); 327 String mapKeyInFirstMap = index1.get(uuidInSecondMapValue); 328 329 if (mapKeyInFirstMap != null) { 330 dualTraverseNodeTypesafe( 331 mapNode1.get(mapKeyInFirstMap), 332 stringMapEntry.getValue(), 333 property.getPseudoPropertyForConfigClassCollectionElement(), 334 allExtensionClasses, 335 filter); 336 337 } 338 339 } 340 341 342 } else { 343 // conf objects without uuids 344 345 try { 346 Map<String, Object> collection1 = (Map<String, Object>) childNode1; 347 Map<String, Object> collection2 = (Map<String, Object>) childNode2; 348 349 for (String key : collection1.keySet()) { 350 dualTraverseNodeTypesafe( 351 collection1.get(key), 352 collection2.get(key), 353 property.getPseudoPropertyForConfigClassCollectionElement(), 354 allExtensionClasses, 355 filter 356 ); 357 } 358 359 } catch (ClassCastException e) { 360 log.warn("Map is malformed", e); 361 } 362 } 363 364 continue; 365 } 366 367 // extensions map 368 if (property.isExtensionsProperty()) { 369 370 try { 371 Map<String, Object> extensionsMap1 = (Map<String, Object>) childNode1; 372 Map<String, Object> extensionsMap2 = (Map<String, Object>) childNode2; 373 374 for (String key : extensionsMap1.keySet()) { 375 try { 376 377 dualTraverseNodeTypesafe( 378 extensionsMap1.get(key), 379 extensionsMap2.get(key), 380 ConfigReflection.getDummyPropertyForClass(Extensions.getExtensionClassBySimpleName(key, allExtensionClasses)), 381 allExtensionClasses, 382 filter 383 ); 384 385 } catch (ClassNotFoundException e) { 386 // noop 387 log.debug("Extension class {} not found, parent node class {} ", key, containerProperty.getRawClass().getName()); 388 } 389 } 390 391 } catch (ClassCastException e) { 392 log.warn("Extensions are malformed", e); 393 } 394 } 395 } 396 } 397 398 public static void traverseMapNode(Object node, AConfigNodeFilter filter) { 399 400 if (node instanceof Map) { 401 Map<String, Object> map = (Map<String, Object>) node; 402 403 filter.beforeNode(map); 404 for (Entry<String, Object> stringObjectEntry : map.entrySet()) { 405 406 String key = stringObjectEntry.getKey(); 407 Object value = stringObjectEntry.getValue(); 408 409 filter.beforeNodeElement(map, key, value); 410 411 if (Nodes.isPrimitive(value)) { 412 filter.onPrimitiveNodeElement(map, key, value); 413 } else if (value instanceof Map) { 414 traverseMapNode(value, filter); 415 } else if (value instanceof List) { 416 417 List list = (List) value; 418 filter.beforeList(list); 419 for (int i = 0; i < list.size(); i++) { 420 421 Object o = list.get(i); 422 423 filter.beforeListElement(list, i, o); 424 425 if (Nodes.isPrimitive(o)) { 426 filter.onPrimitiveListElement(list, o); 427 } else if (o instanceof Map) { 428 traverseMapNode(o, filter); 429 } else { 430 throw new IllegalArgumentException("List is only allowed to contain primitive elements and map nodes. " + 431 "Encountered " + o.getClass() + " in list " + key); 432 } 433 434 filter.afterListElement(list, i, o); 435 } 436 filter.afterList(list); 437 } else { 438 throw new IllegalArgumentException("Illegal node type " + value.getClass() + ", node " + value); 439 } 440 441 filter.afterNodeElement(map, key, value); 442 } 443 444 filter.afterNode(map); 445 446 } else 447 throw new IllegalArgumentException("A composite config node must be a Map<String,Object>"); 448 } 449 450 /** 451 * Traverses with in-depth search and applies dual filter. 452 * 453 * @param node1 454 * @param node2 455 */ 456 public static void dualTraverseMapNodes(Map<String, Object> node1, Map<String, Object> node2, ADualNodeFilter filter) { 457 458 filter.beforeNode(node1, node2); 459 460 if (node1 != null && node2 != null) { 461 462 463 464 if (allEntriesAreNodesAndHaveUuids(node1) && 465 allEntriesAreNodesAndHaveUuids(node2)) { 466 // conf objects with uuids 467 468 Map<String, Map<String, Object>> mapNode1 = (Map) node1; 469 Map<String, Map<String, Object>> mapNode2 = (Map) node2; 470 471 // match on #uuid 472 // extract Map uuid -> index 473 Map<String, String> index1 = new HashMap<String, String>(); 474 for (Entry<String, Map<String, Object>> stringMapEntry : mapNode1.entrySet()) { 475 index1.put( 476 (String) stringMapEntry.getValue().get(Configuration.UUID_KEY), 477 stringMapEntry.getKey() 478 ); 479 } 480 481 for (Entry<String, Map<String, Object>> stringMapEntry : mapNode2.entrySet()) { 482 Object uuidInSecondMapValue = stringMapEntry.getValue().get(Configuration.UUID_KEY); 483 String mapKeyInFirstMap = index1.get(uuidInSecondMapValue); 484 485 if (mapKeyInFirstMap != null) { 486 dualTraverseProperty( 487 mapNode1.get(mapKeyInFirstMap), 488 stringMapEntry.getValue(), 489 filter); 490 } 491 492 } 493 494 495 } else { 496 // conf objects without uuids 497 498 for (Entry<String, Object> objectEntry : node1.entrySet()) 499 if (node2.containsKey(objectEntry.getKey())) { 500 501 filter.beforeNodeProperty(objectEntry.getKey()); 502 503 Object node1El = objectEntry.getValue(); 504 Object node2El = node2.get(objectEntry.getKey()); 505 dualTraverseProperty(node1El, node2El, filter); 506 507 filter.afterNodeProperty(objectEntry.getKey()); 508 } 509 } 510 } 511 512 filter.afterNode(node1, node2); 513 } 514 515 private static void dualTraverseProperty(Object node1El, Object node2El, ADualNodeFilter filter) { 516 if (node2El == null && node1El == null) return; 517 518 if (node1El instanceof List) { 519 520 if (!(node2El instanceof List)) return; 521 522 List list1 = (List) node1El; 523 List list2 = (List) node2El; 524 525 // if any of elements don't have #uuid defined, don't go deeper into the collection 526 // because there is no guarantee that elements will match 527 528 boolean allUuids = allElementsAreNodesAndHaveUuids(list1) 529 && allElementsAreNodesAndHaveUuids(list2) 530 && filter.beforeList(list1, list2); 531 532 if (allUuids) { 533 534 // we are sure by now 535 List<Map<String, Object>> nodeList1 = list1; 536 List<Map<String, Object>> nodeList2 = list2; 537 538 // match on #uuid 539 540 // extract Map uuid -> index 541 Map<String, Integer> index1 = new HashMap<String, Integer>(); 542 543 int i = 0; 544 for (Map<String, Object> node : nodeList1) { 545 index1.put((String) node.get(Configuration.UUID_KEY), i); 546 i++; 547 } 548 549 i = 0; 550 for (Map<String, Object> node : nodeList2) { 551 552 Object uuid = node.get(Configuration.UUID_KEY); 553 Integer elem1Ind = index1.get(uuid); 554 555 if (elem1Ind != null) { 556 // found match 557 filter.beforeListElement(elem1Ind, i); 558 dualTraverseMapNodes(nodeList1.get(elem1Ind), node, filter); 559 filter.afterListElement(elem1Ind, i); 560 } 561 562 i++; 563 } 564 565 } 566 567 filter.afterList(list1, list2); 568 } 569 570 if (node1El instanceof Map) { 571 572 if (!(node2El instanceof Map)) return; 573 574 dualTraverseMapNodes((Map) node1El, (Map) node2El, filter); 575 } 576 577 } 578 579 private static boolean allElementsAreNodesAndHaveUuids(List list) { 580 for (Object o : list) { 581 if (!(o instanceof Map)) return false; 582 if (!(((Map) o).get(Configuration.UUID_KEY) instanceof String)) return false; 583 } 584 return true; 585 } 586 587 private static boolean allEntriesAreNodesAndHaveUuids(Map map) { 588 for (Object o : map.values()) { 589 if (!(o instanceof Map)) return false; 590 if (!(((Map) o).get(Configuration.UUID_KEY) instanceof String)) return false; 591 } 592 return true; 593 } 594 595}