All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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