/*
* 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.
*/
package org.apache.flink.graph.utils.proxy;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.operators.NoOpOperator;
import org.apache.flink.graph.Graph;
import org.apache.flink.graph.GraphAlgorithm;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A {@link GraphAlgorithm} transforms an input {@link Graph} into an output of
* type {@code T}. A {@code GraphAlgorithmWrappingDataSet} wraps the resultant
* {@link DataSet} with a {@code NoOpOperator}. The input to the wrapped
* operator can be replaced when the same algorithm is run on the same input
* with a mergeable configuration. This allows algorithms to be composed of
* implicitly reusable algorithms without publicly sharing intermediate
* {@link DataSet}s.
*
* @param <K> ID type
* @param <VV> vertex value type
* @param <EV> edge value type
* @param <T> output type
*/
public abstract class GraphAlgorithmWrappingDataSet<K, VV, EV, T>
implements GraphAlgorithm<K, VV, EV, DataSet<T>> {
// each algorithm and input pair may map to multiple configurations
private static Map<GraphAlgorithmWrappingDataSet, List<GraphAlgorithmWrappingDataSet>> cache =
Collections.synchronizedMap(new HashMap<GraphAlgorithmWrappingDataSet, List<GraphAlgorithmWrappingDataSet>>());
private Graph<K, VV, EV> input;
private NoOpOperator<T> wrappingOperator;
/**
* Algorithms are identified by name rather than by class to allow subclassing.
*
* @return name of the algorithm, which may be shared by multiple classes
* implementing the same algorithm and generating the same output
*/
protected abstract String getAlgorithmName();
/**
* An algorithm must first test whether the configurations can be merged
* before merging individual fields.
*
* @param other the algorithm with which to compare and merge
* @return true if and only if configuration has been merged and the
* algorithm's output can be reused
*/
protected abstract boolean mergeConfiguration(GraphAlgorithmWrappingDataSet other);
/**
* The implementation of the algorithm, renamed from {@link GraphAlgorithm#run(Graph)}.
*
* @param input the input graph
* @return the algorithm's output
* @throws Exception
*/
protected abstract DataSet<T> runInternal(Graph<K, VV, EV> input) throws Exception;
@Override
public final int hashCode() {
return new HashCodeBuilder(17, 37)
.append(input)
.append(getAlgorithmName())
.toHashCode();
}
@Override
public final boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (! GraphAlgorithmWrappingDataSet.class.isAssignableFrom(obj.getClass())) {
return false;
}
GraphAlgorithmWrappingDataSet rhs = (GraphAlgorithmWrappingDataSet) obj;
return new EqualsBuilder()
.append(input, rhs.input)
.append(getAlgorithmName(), rhs.getAlgorithmName())
.isEquals();
}
@Override
@SuppressWarnings("unchecked")
public final DataSet<T> run(Graph<K, VV, EV> input)
throws Exception {
this.input = input;
if (cache.containsKey(this)) {
for (GraphAlgorithmWrappingDataSet<K, VV, EV, T> other : cache.get(this)) {
if (mergeConfiguration(other)) {
// configuration has been merged so generate new output
DataSet<T> output = runInternal(input);
other.wrappingOperator.setInput(output);
wrappingOperator = other.wrappingOperator;
return wrappingOperator;
}
}
}
// no mergeable configuration found so generate new output
DataSet<T> output = runInternal(input);
// create a new operator to wrap the algorithm output
wrappingOperator = new NoOpOperator<>(output, output.getType());
// cache this result
if (cache.containsKey(this)) {
cache.get(this).add(this);
} else {
cache.put(this, new ArrayList(Collections.singletonList(this)));
}
return wrappingOperator;
}
}