![JAR search and dependency download from the Maven repository](/logo.png)
co.cask.common.internal.io.ReflectionSchemaGenerator Maven / Gradle / Ivy
/*
* 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.common.internal.io;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
/**
* This class uses Java Reflection to inspect fields in any Java class to generate RECORD schema.
*
*
* If the given type is a class, it will uses the class fields (includes all the fields in parent classes)
* to generate the schema. All fields, no matter what access it is would be included, except transient or
* synthetic one.
*
*
*
* If the given type is an interface, it will uses all getter methods (methods that prefix with "get" or "is",
* followed by a name with no arguments) to generate fields for the record schema.
* E.g. for the method {@code String getFirstName()}, a field name "firstName" of type String would be generated.
*
*/
public final class ReflectionSchemaGenerator extends AbstractSchemaGenerator {
@Override
protected Schema generateRecord(TypeToken> typeToken, Set knowRecords, boolean acceptRecursion)
throws UnsupportedTypeException {
String recordName = typeToken.getRawType().getName();
Map> recordFieldTypes =
typeToken.getRawType().isInterface() ?
collectByMethods(typeToken, Maps.>newTreeMap()) :
collectByFields(typeToken, Maps.>newTreeMap());
// Recursively generate field type schema.
ImmutableList.Builder builder = ImmutableList.builder();
for (Map.Entry> fieldType : recordFieldTypes.entrySet()) {
Schema fieldSchema = doGenerate(fieldType.getValue(),
ImmutableSet.builder().addAll(knowRecords).add(recordName).build(),
acceptRecursion);
if (!fieldType.getValue().getRawType().isPrimitive()) {
// For non-primitive, allows "null" value, unless the class is annotated with Nonnull
if (!typeToken.getRawType().isAnnotationPresent(Nonnull.class)) {
fieldSchema = Schema.unionOf(fieldSchema, Schema.of(Schema.Type.NULL));
}
}
builder.add(Schema.Field.of(fieldType.getKey(), fieldSchema));
}
return Schema.recordOf(recordName, builder.build());
}
private Map> collectByFields(TypeToken> typeToken, Map> fieldTypes) {
// Collect the field types
for (TypeToken> classType : typeToken.getTypes().classes()) {
Class> rawType = classType.getRawType();
if (rawType.equals(Object.class)) {
// Ignore all object fields
continue;
}
for (Field field : rawType.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (Modifier.isTransient(modifiers) || Modifier.isStatic(modifiers) || field.isSynthetic()) {
continue;
}
TypeToken> fieldType = classType.resolveType(field.getGenericType());
fieldTypes.put(field.getName(), fieldType);
}
}
return fieldTypes;
}
private Map> collectByMethods(TypeToken> typeToken, Map> fieldTypes) {
for (Method method : typeToken.getRawType().getMethods()) {
if (method.getDeclaringClass().equals(Object.class)) {
// Ignore all object methods
continue;
}
String methodName = method.getName();
if (!(methodName.startsWith("get") || methodName.startsWith("is"))
|| method.isSynthetic() || Modifier.isStatic(method.getModifiers())
|| method.getParameterTypes().length != 0) {
// Ignore not getter methods
continue;
}
String fieldName = methodName.startsWith("get") ?
methodName.substring("get".length()) : methodName.substring("is".length());
if (fieldName.isEmpty()) {
continue;
}
fieldName = String.format("%c%s", Character.toLowerCase(fieldName.charAt(0)), fieldName.substring(1));
if (fieldTypes.containsKey(fieldName)) {
continue;
}
TypeToken> fieldType = typeToken.resolveType(method.getGenericReturnType());
fieldTypes.put(fieldName, fieldType);
}
return fieldTypes;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy