/*
* Copyright © 2014 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.internal.io;
import co.cask.cdap.api.data.schema.UnsupportedTypeException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* Helper class to represent a type parameter in a serializable form. It would be simple if we could just use
* the class name, but we would lose the type parameters of generic classes. If we'd use java.lang.reflect.Type,
* we'd not be able to serialize easily. So here we implement our own representation of the type: it supports
* classes and parametrized classes, it also supports static inner classes. However, it does not support
* interfaces (yet).
*
* This class can be serialized to Json and deserialized back without loss. Because it implements ParametrizedType,
* this class is compatible with TypeToken and we can use it to decode an encoded object of this type.
*/
public final class TypeRepresentation implements ParameterizedType {
private final boolean isClass;
private final String rawType;
private final TypeRepresentation enclosingType;
private final TypeRepresentation[] parameters;
private transient ClassLoader classLoader = null;
/**
* Set the class loader to be used by toType(), getRawType(), etc.
* @param loader the class loader to use
*/
public void setClassLoader(ClassLoader loader) {
this.classLoader = loader;
// Note that this class is immutable after construction, except for the class loader
// Thus we can pass down the loader to the owner and parameter types once now, no need to pass it down in toType()
if (this.enclosingType != null) {
this.enclosingType.setClassLoader(loader);
}
if (this.parameters != null) {
for (TypeRepresentation param: this.parameters) {
param.setClassLoader(loader);
}
}
}
/**
* Constructor from a java Type. For a class, we only remember its name; for a parametrized type, we remember the
* name, the enclosing class, and the type parameters.
* @param type The type to represent
* @throws UnsupportedTypeException
*/
public TypeRepresentation(Type type) throws UnsupportedTypeException {
if (type instanceof Class<?>) {
this.rawType = ((Class) type).getCanonicalName();
this.enclosingType = null;
this.parameters = null;
this.isClass = true;
} else if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Type raw = pType.getRawType();
if (raw instanceof Class<?>) {
this.rawType = ((Class) raw).getName();
} else {
throw new UnsupportedTypeException("can't represent type " + type + " (enclosing type is not a class)");
}
Type owner = pType.getOwnerType();
this.enclosingType = owner == null ? null : new TypeRepresentation(owner);
Type[] typeArgs = pType.getActualTypeArguments();
this.parameters = new TypeRepresentation[typeArgs.length];
for (int i = 0; i < typeArgs.length; i++) {
this.parameters[i] = new TypeRepresentation(typeArgs[i]);
}
this.isClass = false;
} else {
throw new UnsupportedTypeException("can't represent type " + type + " (must be a class or a parametrized type)");
}
}
/**
* @return the represented Type. Note that for a parametrized type, we can just return this,
* because it implements the ParametrizedType interface.
*/
public Type toType() {
if (this.isClass) {
return this.getRawType();
} else {
return this;
}
}
@Override
public Type[] getActualTypeArguments() {
if (this.parameters == null) {
return null;
}
Type[] typeArgs = new Type[this.parameters.length];
for (int i = 0; i < typeArgs.length; i++) {
typeArgs[i] = this.parameters[i].toType();
}
return typeArgs;
}
@Override
public Type getRawType() {
try {
ClassLoader cl = classLoader;
if (cl == null) {
cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = getClass().getClassLoader();
}
}
return cl.loadClass(this.rawType);
} catch (ClassNotFoundException e) {
throw new RuntimeException("cannot convert " + this.rawType + " to a type. ", e);
}
}
@Override
public Type getOwnerType() {
return this.enclosingType == null ? null : this.enclosingType.toType();
}
} // TypeRepresentation