| #* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| *# |
| #parse ( "common.vm" ) |
| # |
| #set ( $package = "${packageToolV4}" ) |
| #set ( $className = "${model.name}Merger" ) |
| # |
| #set ( $root = $model.getClass( $model.getRoot($version), $version ) ) |
| # |
| #MODELLO-VELOCITY#SAVE-OUTPUT-TO ${package.replace('.','/')}/${className}.java |
| // =================== DO NOT EDIT THIS FILE ==================== |
| // Generated by Modello Velocity from ${template} |
| // template, any modifications will be overwritten. |
| // ============================================================== |
| package ${package}; |
| |
| import java.io.ObjectStreamException; |
| import java.util.AbstractList; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.function.BinaryOperator; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| |
| import org.apache.maven.api.annotations.Generated; |
| import org.apache.maven.api.xml.XmlNode; |
| #foreach ( $class in $model.allClasses ) |
| import ${packageModelV4}.${class.Name}; |
| #end |
| |
| @Generated |
| public class ${className} { |
| |
| private final boolean deepMerge; |
| |
| public ${className}() { |
| this(true); |
| } |
| |
| public ${className}(boolean deepMerge) { |
| this.deepMerge = deepMerge; |
| } |
| |
| /** |
| * Merges the specified source object into the given target object. |
| * |
| * @param target The target object whose existing contents should be merged with the source, must not be |
| * <code>null</code>. |
| * @param source The (read-only) source object that should be merged into the target object, may be |
| * <code>null</code>. |
| * @param sourceDominant A flag indicating whether either the target object or the source object provides the |
| * dominant data. |
| * @param hints A set of key-value pairs that customized merger implementations can use to carry domain-specific |
| * information along, may be <code>null</code>. |
| */ |
| public ${root.name} merge(${root.name} target, ${root.name} source, boolean sourceDominant, Map<?, ?> hints) { |
| Objects.requireNonNull(target, "target cannot be null"); |
| if (source == null) { |
| return target; |
| } |
| Map<Object, Object> context = new HashMap<>(); |
| if (hints != null) { |
| context.putAll(hints); |
| } |
| return merge${root.name}(target, source, sourceDominant, context); |
| } |
| |
| #foreach ( $class in $model.allClasses ) |
| #if ( $class.name != "InputSource" && $class.name != "InputLocation" ) |
| #set ( $ancestors = $Helper.ancestors( $class ) ) |
| #set ( $allFields = [] ) |
| #foreach ( $cl in $ancestors ) |
| #set ( $dummy = $allFields.addAll( $cl.getFields($version) ) ) |
| #end |
| protected ${class.name} merge${class.name}(${class.name} target, ${class.name} source, boolean sourceDominant, Map<Object, Object> context) { |
| ${class.name}.Builder builder = ${class.name}.newBuilder(target); |
| merge${class.name}(builder, target, source, sourceDominant, context); |
| return builder.build(); |
| } |
| |
| protected void merge${class.name}(${class.name}.Builder builder, ${class.name} target, ${class.name} source, boolean sourceDominant, Map<Object, Object> context) { |
| #if ( $class.superClass ) |
| merge${class.superClass}(builder, target ,source, sourceDominant, context); |
| #end |
| #foreach ( $field in $class.getFields($version) ) |
| merge${field.modelClass.name}_${Helper.capitalise($field.name)}(builder, target, source, sourceDominant, context); |
| #end |
| } |
| |
| #foreach ( $field in $allFields ) |
| #set ( $capField = ${Helper.capitalise($field.name)} ) |
| protected void merge${class.name}_${capField}(${class.name}.Builder builder, ${class.name} target, ${class.name} source, boolean sourceDominant, Map<Object, Object> context) |
| { |
| #if ( $field.type == "String" ) |
| String src = source.get${capField}(); |
| String tgt = target.get${capField}(); |
| if (src != null && (sourceDominant || tgt == null)) { |
| builder.${field.name}(src); |
| #if ( $locationTracking ) |
| builder.location("${field.name}", source.getLocation("${field.name}")); |
| #end |
| } |
| #elseif ( $field.type == "java.util.List" && $field.to == "String" && $field.multiplicity == "*" ) |
| builder.${field.name}(merge(target.get${capField}(), source.get${capField}(), sourceDominant, e -> e)); |
| #elseif ( $field.type == "java.util.Properties" && $field.to == "String" && $field.multiplicity == "*" ) |
| Map<String, String> src = source.get${capField}(); |
| if (!src.isEmpty()) { |
| Map<String, String> tgt = target.get${capField}(); |
| if (tgt.isEmpty()) { |
| builder.${field.name}(src); |
| #if ( $locationTracking ) |
| builder.location("${field.name}", source.getLocation("${field.name}")); |
| #end |
| } else { |
| Map<String, String> merged = new HashMap<>(); |
| merged.putAll(sourceDominant ? target.get${capField}() : source.get${capField}()); |
| merged.putAll(sourceDominant ? source.get${capField}() : target.get${capField}()); |
| builder.${field.name}(merged); |
| #if ( $locationTracking ) |
| builder.location("${field.name}", InputLocation.merge(target.getLocation("${field.name}"), source.getLocation("${field.name}"), sourceDominant)); |
| #end |
| } |
| } |
| #elseif ( $field.to && $field.multiplicity == "1" ) |
| ${field.to} src = source.get${capField}(); |
| if (src != null) { |
| ${field.to} tgt = target.get${capField}(); |
| if (tgt == null) { |
| tgt = ${field.to}.newInstance(false); |
| } |
| ${field.to} merged = merge${field.to}(tgt, src, sourceDominant, context); |
| if (merged == src) { |
| builder.${field.name}(merged); |
| #if ( $locationTracking ) |
| builder.location("${field.name}", source.getLocation("${field.name}")); |
| #end |
| } else if (merged != tgt) { |
| builder.${field.name}(merged); |
| #if ( $locationTracking ) |
| builder.location("${field.name}", InputLocation.merge(target.getLocation("${field.name}"), source.getLocation("${field.name}"), sourceDominant)); |
| #end |
| } |
| } |
| #elseif ( $field.to && $field.multiplicity == "*" ) |
| if (deepMerge) { |
| builder.${field.name}(merge(target.get${capField}(), source.get${capField}(), get${field.to}Key(), |
| (t, s) -> merge${field.to}(t, s, sourceDominant, context))); |
| } else { |
| builder.${field.name}(merge(target.get${capField}(), source.get${capField}(), sourceDominant, get${field.to}Key())); |
| } |
| #elseif ( $field.type == "DOM" ) |
| XmlNode src = source.getConfiguration(); |
| if (src != null) { |
| XmlNode tgt = target.getConfiguration(); |
| if (tgt == null) { |
| builder.configuration(src); |
| } else if (sourceDominant) { |
| builder.configuration(src.merge(tgt)); |
| } else { |
| builder.configuration(tgt.merge(src)); |
| } |
| } |
| #elseif ($field.type == "boolean") |
| if (sourceDominant) { |
| builder.${field.name}(source.is${capField}()); |
| } |
| #elseif ($field.type == "int" || $field.type == "java.nio.file.Path") |
| if (sourceDominant) { |
| builder.${field.name}(source.get${capField}()); |
| } |
| #else |
| // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity} |
| #end |
| } |
| #end |
| |
| #end |
| #end |
| |
| #foreach ( $class in $model.allClasses ) |
| #if ( $class.name != "InputSource" && $class.name != "InputLocation" ) |
| protected KeyComputer<${class.name}> get${class.name}Key() { |
| return v -> v; |
| } |
| #end |
| #end |
| |
| /** |
| * Use to compute keys for data structures |
| * @param <T> the data structure type |
| */ |
| @FunctionalInterface |
| public interface KeyComputer<T> extends Function<T, Object> { |
| } |
| |
| /** |
| * Merge two lists |
| */ |
| public static <T> List<T> merge(List<T> tgt, List<T> src, boolean sourceDominant, KeyComputer<T> computer) { |
| return merge(tgt, src, computer, (t, s) -> sourceDominant ? s : t); |
| } |
| |
| public static <T> List<T> merge(List<T> tgt, List<T> src, KeyComputer<T> computer, BinaryOperator<T> remapping) { |
| if (src.isEmpty()) { |
| return tgt; |
| } |
| |
| MergingList<T> list; |
| if (tgt instanceof MergingList) { |
| list = (MergingList<T>) tgt; |
| } else { |
| list = new MergingList<>(computer, src.size() + tgt.size()); |
| list.mergeAll(tgt, (t, s) -> s); |
| } |
| |
| list.mergeAll(src, remapping); |
| return list; |
| } |
| |
| /** |
| * Merging list |
| * @param <V> |
| */ |
| private static class MergingList<V> extends AbstractList<V> implements java.io.Serializable { |
| |
| private final KeyComputer<V> keyComputer; |
| private Map<Object, V> map; |
| private List<V> list; |
| |
| MergingList(KeyComputer<V> keyComputer, int initialCapacity) { |
| this.map = new LinkedHashMap<>(initialCapacity); |
| this.keyComputer = keyComputer; |
| } |
| |
| Object writeReplace() throws ObjectStreamException { |
| return new ArrayList<>(this); |
| } |
| |
| @Override |
| public Iterator<V> iterator() { |
| if (map != null) { |
| return map.values().iterator(); |
| } else { |
| return list.iterator(); |
| } |
| } |
| |
| void mergeAll(Collection<V> vs, BinaryOperator<V> remapping) { |
| if (map == null) { |
| map = list.stream().collect(Collectors.toMap(keyComputer, |
| Function.identity(), |
| null, |
| LinkedHashMap::new)); |
| list = null; |
| } |
| |
| if (vs instanceof MergingList && ((MergingList<V>) vs).map != null) { |
| for (Map.Entry<Object, V> e : ((MergingList<V>) vs).map.entrySet()) { |
| Object key = e.getKey(); |
| V v = e.getValue(); |
| map.merge(key, v, remapping); |
| } |
| } else { |
| for (V v : vs) { |
| Object key = keyComputer.apply(v); |
| map.merge(key, v, remapping); |
| } |
| } |
| } |
| |
| @Override |
| public boolean contains(Object o) { |
| if (map != null) { |
| return map.containsValue(o); |
| } else { |
| return list.contains(o); |
| } |
| } |
| |
| private List<V> asList() { |
| if (list == null) { |
| list = new ArrayList<>(map.values()); |
| map = null; |
| } |
| return list; |
| } |
| |
| @Override |
| public void add(int index, V element) { |
| asList().add(index, element); |
| } |
| |
| @Override |
| public V remove(int index) { |
| return asList().remove(index); |
| } |
| |
| @Override |
| public V get(int index) { |
| return asList().get(index); |
| } |
| |
| @Override |
| public int size() { |
| if (map != null) { |
| return map.size(); |
| } else { |
| return list.size(); |
| } |
| } |
| } |
| } |