001/*
002 * Copyright (C) 2012 Facebook, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may
005 * not use this file except in compliance with the License. You may obtain
006 * a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations
014 * under the License.
015 */
016package com.facebook.swift.codec.metadata;
017
018import com.facebook.swift.codec.ThriftEnumValue;
019import com.google.common.base.Preconditions;
020import com.google.common.collect.ImmutableList;
021import com.google.common.collect.ImmutableMap;
022
023import java.lang.reflect.Method;
024import java.lang.reflect.Modifier;
025import java.util.Map;
026
027import javax.annotation.concurrent.Immutable;
028
029import static java.lang.String.format;
030
031@Immutable
032public class ThriftEnumMetadata<T extends Enum<T>>
033{
034    private final Class<T> enumClass;
035    private final Map<Integer, T> byEnumValue;
036    private final Map<T, Integer> byEnumConstant;
037    private final String enumName;
038    private final ImmutableList<String> documentation;
039    private final ImmutableMap<T, ImmutableList<String>> elementDocs;
040
041    public ThriftEnumMetadata(
042            String enumName,
043            Class<T> enumClass)
044            throws RuntimeException
045    {
046        Preconditions.checkNotNull(enumName, "enumName must not be null");
047        Preconditions.checkNotNull(enumClass, "enumClass must not be null");
048
049        this.enumName = enumName;
050        this.enumClass = enumClass;
051
052        Method enumValueMethod = null;
053        for (Method method : enumClass.getMethods()) {
054            if (method.isAnnotationPresent(ThriftEnumValue.class)) {
055                Preconditions.checkArgument(
056                        Modifier.isPublic(method.getModifiers()),
057                        "Enum class %s @ThriftEnumValue method is not public: %s",
058                        enumClass.getName(),
059                        method);
060                Preconditions.checkArgument(
061                        !Modifier.isStatic(method.getModifiers()),
062                        "Enum class %s @ThriftEnumValue method is static: %s",
063                        enumClass.getName(),
064                        method);
065                Preconditions.checkArgument(
066                        method.getTypeParameters().length == 0,
067                        "Enum class %s @ThriftEnumValue method has parameters: %s",
068                        enumClass.getName(),
069                        method);
070                Class<?> returnType = method.getReturnType();
071                Preconditions.checkArgument(
072                        returnType == int.class || returnType == Integer.class,
073                        "Enum class %s @ThriftEnumValue method does not return int or Integer: %s",
074                        enumClass.getName(),
075                        method);
076                enumValueMethod = method;
077            }
078        }
079
080        ImmutableMap.Builder<T, ImmutableList<String>> elementDocs = ImmutableMap.builder();
081        if (enumValueMethod != null) {
082            ImmutableMap.Builder<Integer, T> byEnumValue = ImmutableMap.builder();
083            ImmutableMap.Builder<T, Integer> byEnumConstant = ImmutableMap.builder();
084            for (T enumConstant : enumClass.getEnumConstants()) {
085                Integer value;
086                try {
087                    value = (Integer) enumValueMethod.invoke(enumConstant);
088                }
089                catch (Exception e) {
090                    throw new RuntimeException(format("Enum class %s element %s get value method threw an exception", enumClass.getName(), enumConstant), e);
091                }
092                Preconditions.checkArgument(
093                        value != null,
094                        "Enum class %s element %s returned null for enum value: %s",
095                        enumClass.getName(),
096                        enumConstant
097                );
098
099                byEnumValue.put(value, enumConstant);
100                byEnumConstant.put(enumConstant, value);
101                elementDocs.put(enumConstant, ThriftCatalog.getThriftDocumentation(enumConstant));
102            }
103            this.byEnumValue = byEnumValue.build();
104            this.byEnumConstant = byEnumConstant.build();
105        }
106        else {
107            byEnumValue = null;
108            byEnumConstant = null;
109            for (T enumConstant : enumClass.getEnumConstants()) {
110                elementDocs.put(enumConstant, ThriftCatalog.getThriftDocumentation(enumConstant));
111            }
112        }
113        this.elementDocs = elementDocs.build();
114        this.documentation = ThriftCatalog.getThriftDocumentation(enumClass);
115    }
116
117    public String getEnumName()
118    {
119        return enumName;
120    }
121
122    public Class<T> getEnumClass()
123    {
124        return enumClass;
125    }
126
127    public boolean hasExplicitThriftValue()
128    {
129        return byEnumValue != null;
130    }
131
132    public Map<Integer, T> getByEnumValue()
133    {
134        return byEnumValue;
135    }
136
137    public Map<T, Integer> getByEnumConstant()
138    {
139        return byEnumConstant;
140    }
141
142    public ImmutableList<String> getDocumentation()
143    {
144        return documentation;
145    }
146
147    public Map<T, ImmutableList<String>> getElementsDocumentation()
148    {
149        return elementDocs;
150    }
151
152    @Override
153    public boolean equals(Object o)
154    {
155        if (this == o) {
156            return true;
157        }
158        if (o == null || getClass() != o.getClass()) {
159            return false;
160        }
161
162        final ThriftEnumMetadata<?> that = (ThriftEnumMetadata<?>) o;
163
164        if (!enumClass.equals(that.enumClass)) {
165            return false;
166        }
167
168        return true;
169    }
170
171    @Override
172    public int hashCode()
173    {
174        return enumClass.hashCode();
175    }
176
177    @Override
178    public String toString()
179    {
180        final StringBuilder sb = new StringBuilder();
181        sb.append("ThriftEnumMetadata");
182        sb.append("{enumClass=").append(enumClass);
183        sb.append(", byThriftValue=").append(byEnumValue);
184        sb.append('}');
185        return sb.toString();
186    }
187}