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}