blob: 37238787d7ef82b47dda47f1432e7ae3e13b0f14 [file] [log] [blame]
#*
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();
}
}
}
}