/*
* Copyright © 2016 Cask Data, Inc.
*
* Licensed 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 co.cask.cdap.etl.common;
import co.cask.cdap.etl.common.guice.TypeResolver;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;
/**
* Validates types passed between pipeline stages.
*
* TODO : CDAP-4387 Validate Stages has been removed due to DAG implementation, have to be refactored
*/
public class PipelineTypeValidator {
/**
* Takes in an unresolved type list and resolves the types and verifies if the types are assignable.
* Ex: An unresolved type could be : String, T, List<T>, List<String>
* The above will resolve to : String, String, List<String>, List<String>
* And the assignability will be checked : String --> String && List<String> --> List<String>
* which is true in the case above.
*/
@VisibleForTesting
static void validateTypes(List<Type> unresTypeList) {
Preconditions.checkArgument(unresTypeList.size() % 2 == 0, "ETL Stages validation expects even number of types");
List<Type> resTypeList = Lists.newArrayListWithCapacity(unresTypeList.size());
// Add the source output to resolved type list as the first resolved type.
resTypeList.add(unresTypeList.get(0));
try {
// Resolve the second type using just the first resolved type.
Type nType = (new TypeResolver()).where(unresTypeList.get(1), resTypeList.get(0)).resolveType(
unresTypeList.get(1));
resTypeList.add(nType);
} catch (IllegalArgumentException e) {
// If unable to resolve type, add the second type as is, to the resolved list.
resTypeList.add(unresTypeList.get(1));
}
for (int i = 2; i < unresTypeList.size(); i++) {
// ActualType is previous resolved type; FormalType is previous unresolved type;
// ToResolveType is current unresolved type;
// Ex: Actual = String; Formal = T; ToResolve = List<T>; ==> newType = List<String>
Type actualType = resTypeList.get(i - 1);
Type formalType = unresTypeList.get(i - 1);
Type toResolveType = unresTypeList.get(i);
try {
Type newType;
// If the toResolveType is a TypeVariable or a Generic Array, then try to resolve
// using just the previous resolved type.
// Ex: Actual = List<String> ; Formal = List<T> ; ToResolve = T ==> newType = String which is not correct;
// newType should be List<String>. Hence resolve only from the previous resolved type (Actual)
if ((toResolveType instanceof TypeVariable) || (toResolveType instanceof GenericArrayType)) {
newType = (new TypeResolver()).where(toResolveType, actualType).resolveType(toResolveType);
} else {
newType = (new TypeResolver()).where(formalType, actualType).resolveType(toResolveType);
}
resTypeList.add(newType);
} catch (IllegalArgumentException e) {
// If resolution failed, add the type as is to the resolved list.
resTypeList.add(toResolveType);
}
}
// Check isAssignable on the resolved list for every paired elements. 0 --> 1 | 2 --> 3 | 4 --> 5 where | is a
// transform (which takes in type on its left and emits the type on its right).
for (int i = 0; i < resTypeList.size(); i += 2) {
Type firstType = resTypeList.get(i);
Type secondType = resTypeList.get(i + 1);
// Check if secondType can accept firstType
Preconditions.checkArgument(TypeToken.of(secondType).isAssignableFrom(firstType),
"Types between stages didn't match. Mismatch between {} -> {}",
firstType, secondType);
}
}
}