/*
* 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.api.java.sca;
import org.apache.flink.annotation.Internal;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.analysis.BasicValue;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
/**
* Extension of ASM's BasicValue that allows to assign "tags"
* to values and add additional information depending on the tag to the Value.
*/
@Internal
public class TaggedValue extends BasicValue {
public static enum Tag {
REGULAR, // regular object with no special meaning
THIS, // a special container which is the instance of the UDF
INPUT, // atomic input field
COLLECTOR, // collector of UDF
CONTAINER, // container that contains fields
INT_CONSTANT, // int constant
INPUT_1_ITERABLE, INPUT_2_ITERABLE, INPUT_1_ITERATOR, INPUT_2_ITERATOR, // input iterators
ITERATOR_TRUE_ASSUMPTION, // boolean value that is "true" at least once
NULL // null
}
public static enum Input {
INPUT_1(0), INPUT_2(1);
private int id;
private Input(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
private Tag tag;
// only inputs can set this to true
private boolean callByValue = false;
// for inputs
private Input input;
private String flatFieldExpr; // empty string for non-composite types
private boolean grouped;
// for input containers & this
// key=field / value=input or container
// undefined state => value=null
private Map<String, TaggedValue> containerMapping;
private Map<String, ModifiedASMFrame> containerFrameMapping;
// for int constants
private int intConstant;
public TaggedValue(Type type) {
this(type, Tag.REGULAR);
}
public TaggedValue(Type type, Tag tag) {
super(type);
this.tag = tag;
}
public TaggedValue(Type type, Input input, String flatFieldExpr, boolean grouped, boolean callByValue) {
super(type);
tag = Tag.INPUT;
this.input = input;
this.flatFieldExpr = flatFieldExpr;
this.grouped = grouped;
this.callByValue = callByValue;
}
public TaggedValue(Type type, Map<String, TaggedValue> containerMapping) {
super(type);
tag = Tag.CONTAINER;
this.containerMapping = containerMapping;
}
public TaggedValue(int constant) {
super(Type.INT_TYPE);
tag = Tag.INT_CONSTANT;
this.intConstant = constant;
}
public boolean isInput() {
return tag == Tag.INPUT;
}
public boolean isThis() {
return tag == Tag.THIS;
}
public boolean isContainer() {
return tag == Tag.CONTAINER;
}
public boolean isRegular() {
return tag == Tag.REGULAR;
}
public boolean isIntConstant() {
return tag == Tag.INT_CONSTANT;
}
public boolean isCollector() {
return tag == Tag.COLLECTOR;
}
public boolean isInputIterable() {
return tag == Tag.INPUT_1_ITERABLE || tag == Tag.INPUT_2_ITERABLE;
}
public boolean isInputIterator() {
return tag == Tag.INPUT_1_ITERATOR || tag == Tag.INPUT_2_ITERATOR;
}
public boolean isInput1Iterable() {
return tag == Tag.INPUT_1_ITERABLE;
}
public boolean isInput1Iterator() {
return tag == Tag.INPUT_1_ITERATOR;
}
public boolean isIteratorTrueAssumption() {
return tag == Tag.ITERATOR_TRUE_ASSUMPTION;
}
public boolean isNull() {
return tag == Tag.NULL;
}
public boolean canNotContainInput() {
return tag != Tag.INPUT && tag != Tag.CONTAINER && tag != Tag.THIS;
}
public boolean canContainInput() {
return tag == Tag.INPUT || tag == Tag.CONTAINER || tag == Tag.THIS;
}
public boolean canContainFields() {
return tag == Tag.CONTAINER || tag == Tag.THIS;
}
public boolean isCallByValue() {
return callByValue;
}
public Tag getTag() {
return tag;
}
public void setTag(Tag tag) {
this.tag = tag;
if (tag == Tag.CONTAINER || tag == Tag.THIS) {
input = null;
flatFieldExpr = null;
}
else if (tag == Tag.INPUT) {
containerMapping = null;
}
else {
input = null;
containerMapping = null;
flatFieldExpr = null;
}
}
public String toForwardedFieldsExpression(Input input) {
// input not relevant
if (isInput() && this.input != input) {
return null;
}
// equivalent to semantic annotation "*" for non-composite types
else if (isInput() && flatFieldExpr.length() == 0) {
return "*";
}
// equivalent to "f0.f0->*"
else if (isInput()) {
return flatFieldExpr + "->*";
}
// equivalent to "f3;f0.f0->f0.f1;f1->f2;..."
else if (canContainFields() && containerMapping != null) {
final StringBuilder sb = new StringBuilder();
traverseContainer(input, containerMapping, sb, "");
final String returnValue = sb.toString();
if (returnValue != null && returnValue.length() > 0) {
return returnValue;
}
}
return null;
}
private void traverseContainer(Input input, Map<String, TaggedValue> containerMapping, StringBuilder sb,
String prefix) {
for (Map.Entry<String,TaggedValue> entry : containerMapping.entrySet()) {
// skip undefined states
if (entry.getValue() == null) {
continue;
}
// input
else if (entry.getValue().isInput() && entry.getValue().input == input) {
final String flatFieldExpr = entry.getValue().getFlatFieldExpr();
if (flatFieldExpr.length() == 0) {
sb.append("*");
}
else {
sb.append(flatFieldExpr);
}
sb.append("->");
if (prefix.length() > 0) {
sb.append(prefix);
sb.append('.');
}
sb.append(entry.getKey());
sb.append(';');
}
// input containers
else if (entry.getValue().canContainFields()) {
traverseContainer(input, entry.getValue().containerMapping, sb,
((prefix.length() > 0)? prefix + "." : "") + entry.getKey());
}
}
}
@Override
public boolean equals(Object value) {
if (!(value instanceof TaggedValue) || !super.equals(value)) {
return false;
}
final TaggedValue other = (TaggedValue) value;
if (other.tag != tag) {
return false;
}
if (isInput()) {
return input == other.input && flatFieldExpr.equals(other.flatFieldExpr)
&& grouped == other.grouped && callByValue == other.callByValue;
}
else if (canContainFields()) {
if ((containerMapping == null && other.containerMapping != null)
|| (containerMapping != null && other.containerMapping == null)) {
return false;
}
if (containerMapping == null) {
return true;
}
return containerMapping.equals(other.containerMapping);
}
return tag == other.tag;
}
@Override
public String toString() {
if (isInput()) {
return "TaggedValue(" + tag + ":" + flatFieldExpr + ")";
}
else if (canContainFields()) {
return "TaggedValue(" + tag + ":" + containerMapping + ")";
}
else if (isIntConstant()) {
return "TaggedValue(" + tag + ":" + intConstant + ")";
}
return "TaggedValue(" + tag + ")";
}
// --------------------------------------------------------------------------------------------
// Input
// --------------------------------------------------------------------------------------------
public Input getInput() {
return input;
}
public String getFlatFieldExpr() {
return flatFieldExpr;
}
public boolean isGrouped() {
return grouped;
}
// --------------------------------------------------------------------------------------------
// Container & This
// --------------------------------------------------------------------------------------------
public Map<String, TaggedValue> getContainerMapping() {
return containerMapping;
}
public boolean containerContains(String field) {
if (containerMapping == null) {
return false;
}
return containerMapping.containsKey(field);
}
public boolean containerHasReferences() {
if (containerMapping == null) {
return false;
}
for (TaggedValue value : containerMapping.values()) {
if (value == null || !value.isCallByValue()) {
return true;
}
}
return false;
}
public void addContainerMapping(String field, TaggedValue mapping, ModifiedASMFrame frame) {
if (containerMapping == null) {
containerMapping = new HashMap<String, TaggedValue>(4);
}
if (containerFrameMapping == null) {
containerFrameMapping = new HashMap<String, ModifiedASMFrame>(4);
}
if (containerMapping.containsKey(field)
&& containerMapping.get(field) != null
&& frame == containerFrameMapping.get(field)) {
containerMapping.put(field, null);
containerFrameMapping.remove(field);
}
else {
containerMapping.put(field, mapping);
containerFrameMapping.put(field, frame);
}
}
public void clearContainerMappingMarkedFields() {
if (containerMapping != null) {
final Iterator<Entry<String, TaggedValue>> it = containerMapping.entrySet().iterator();
while (it.hasNext()) {
final Entry<String, TaggedValue> entry = it.next();
if (entry.getValue() == null) {
it.remove();
}
}
}
}
public void makeRegular() {
if (canContainFields() && containerMapping != null) {
for (TaggedValue value : containerMapping.values()) {
value.makeRegular();
}
}
setTag(Tag.REGULAR);
}
// --------------------------------------------------------------------------------------------
// IntConstant
// --------------------------------------------------------------------------------------------
public int getIntConstant() {
return intConstant;
}
public TaggedValue copy() {
return copy(getType());
}
public TaggedValue copy(Type type) {
final TaggedValue newValue = new TaggedValue(type);
newValue.tag = this.tag;
if (isInput()) {
newValue.input = this.input;
newValue.flatFieldExpr = this.flatFieldExpr;
newValue.grouped = this.grouped;
newValue.callByValue = this.callByValue;
}
else if (canContainFields()) {
final HashMap<String, TaggedValue> containerMapping = new HashMap<String, TaggedValue>(this.containerMapping.size());
final HashMap<String, ModifiedASMFrame> containerFrameMapping;
if (this.containerFrameMapping != null) {
containerFrameMapping = new HashMap<String, ModifiedASMFrame>(this.containerFrameMapping.size());
} else {
containerFrameMapping = null;
}
for (Entry<String, TaggedValue> entry : this.containerMapping.entrySet()) {
if (entry.getValue() != null) {
containerMapping.put(entry.getKey(), entry.getValue().copy());
if (containerFrameMapping != null) {
containerFrameMapping.put(entry.getKey(), this.containerFrameMapping.get(entry.getKey()));
}
} else {
containerMapping.put(entry.getKey(), null);
}
}
newValue.containerMapping = containerMapping;
newValue.containerFrameMapping = containerFrameMapping;
}
else if (isIntConstant()) {
newValue.intConstant = this.intConstant;
}
return newValue;
}
}