ThriftEnumMetadata.java

/*
 * Copyright (C) 2012 Facebook, 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 com.facebook.swift.codec.metadata;

import com.facebook.swift.codec.ThriftEnumValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

import javax.annotation.concurrent.Immutable;

import static java.lang.String.format;

@Immutable
public class ThriftEnumMetadata<T extends Enum<T>>
{
    private final Class<T> enumClass;
    private final Map<Integer, T> byEnumValue;
    private final Map<T, Integer> byEnumConstant;

    public ThriftEnumMetadata(Class<T> enumClass)
            throws RuntimeException
    {
        Preconditions.checkNotNull(enumClass, "enumClass must not be null");
        this.enumClass = enumClass;

        Method enumValueMethod = null;
        for (Method method : enumClass.getMethods()) {
            if (method.isAnnotationPresent(ThriftEnumValue.class)) {
                Preconditions.checkArgument(
                        Modifier.isPublic(method.getModifiers()),
                        "Enum class %s @ThriftEnumValue method is not public: %s",
                        enumClass.getName(),
                        method);
                Preconditions.checkArgument(
                        !Modifier.isStatic(method.getModifiers()),
                        "Enum class %s @ThriftEnumValue method is static: %s",
                        enumClass.getName(),
                        method);
                Preconditions.checkArgument(
                        method.getTypeParameters().length == 0,
                        "Enum class %s @ThriftEnumValue method has parameters: %s",
                        enumClass.getName(),
                        method);
                Class<?> returnType = method.getReturnType();
                Preconditions.checkArgument(
                        returnType == int.class || returnType == Integer.class,
                        "Enum class %s @ThriftEnumValue method does not return int or Integer: %s",
                        enumClass.getName(),
                        method);
                enumValueMethod = method;
            }
        }

        if (enumValueMethod != null) {
            ImmutableMap.Builder<Integer, T> byEnumValue = ImmutableMap.builder();
            ImmutableMap.Builder<T, Integer> byEnumConstant = ImmutableMap.builder();
            for (T enumConstant : enumClass.getEnumConstants()) {
                Integer value;
                try {
                    value = (Integer) enumValueMethod.invoke(enumConstant);
                }
                catch (Exception e) {
                    throw new RuntimeException(format("Enum class %s element %s get value method threw an exception", enumClass.getName(), enumConstant), e);
                }
                Preconditions.checkArgument(
                        value != null,
                        "Enum class %s element %s returned null for enum value: %s",
                        enumClass.getName(),
                        enumConstant
                );

                byEnumValue.put(value, enumConstant);
                byEnumConstant.put(enumConstant, value);
            }
            this.byEnumValue = byEnumValue.build();
            this.byEnumConstant = byEnumConstant.build();
        }
        else {
            byEnumValue = null;
            byEnumConstant = null;
        }
    }

    public Class<T> getEnumClass()
    {
        return enumClass;
    }

    public boolean hasExplicitThriftValue()
    {
        return byEnumValue != null;
    }

    public Map<Integer, T> getByEnumValue()
    {
        return byEnumValue;
    }

    public Map<T, Integer> getByEnumConstant()
    {
        return byEnumConstant;
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        final ThriftEnumMetadata<?> that = (ThriftEnumMetadata<?>) o;

        if (!enumClass.equals(that.enumClass)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode()
    {
        return enumClass.hashCode();
    }

    @Override
    public String toString()
    {
        final StringBuilder sb = new StringBuilder();
        sb.append("ThriftEnumMetadata");
        sb.append("{enumClass=").append(enumClass);
        sb.append(", byThriftValue=").append(byEnumValue);
        sb.append('}');
        return sb.toString();
    }
}