1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.hardware.camera2.marshal.impl;
17 
18 import android.hardware.camera2.marshal.Marshaler;
19 import android.hardware.camera2.marshal.MarshalQueryable;
20 import android.hardware.camera2.utils.TypeReference;
21 import android.util.Log;
22 
23 import java.nio.ByteBuffer;
24 import java.util.HashMap;
25 
26 import static android.hardware.camera2.impl.CameraMetadataNative.*;
27 import static android.hardware.camera2.marshal.MarshalHelpers.*;
28 
29 /**
30  * Marshal any simple enum (0-arg constructors only) into/from either
31  * {@code TYPE_BYTE} or {@code TYPE_INT32}.
32  *
33  * <p>Default values of the enum are mapped to its ordinal; this can be overridden
34  * by providing a manual value with {@link #registerEnumValues}.</p>
35 
36  * @param <T> the type of {@code Enum}
37  */
38 public class MarshalQueryableEnum<T extends Enum<T>> implements MarshalQueryable<T> {
39 
40     private static final String TAG = MarshalQueryableEnum.class.getSimpleName();
41     private static final boolean DEBUG = false;
42 
43     private static final int UINT8_MIN = 0x0;
44     private static final int UINT8_MAX = (1 << Byte.SIZE) - 1;
45     private static final int UINT8_MASK = UINT8_MAX;
46 
47     private class MarshalerEnum extends Marshaler<T> {
48 
49         private final Class<T> mClass;
50 
51         @SuppressWarnings("unchecked")
MarshalerEnum(TypeReference<T> typeReference, int nativeType)52         protected MarshalerEnum(TypeReference<T> typeReference, int nativeType) {
53             super(MarshalQueryableEnum.this, typeReference, nativeType);
54 
55             mClass = (Class<T>)typeReference.getRawType();
56         }
57 
58         @Override
marshal(T value, ByteBuffer buffer)59         public void marshal(T value, ByteBuffer buffer) {
60             int enumValue = getEnumValue(value);
61 
62             if (mNativeType == TYPE_INT32) {
63                 buffer.putInt(enumValue);
64             } else if (mNativeType == TYPE_BYTE) {
65                 if (enumValue < UINT8_MIN || enumValue > UINT8_MAX) {
66                     throw new UnsupportedOperationException(String.format(
67                             "Enum value %x too large to fit into unsigned byte", enumValue));
68                 }
69                 buffer.put((byte)enumValue);
70             } else {
71                 throw new AssertionError();
72             }
73         }
74 
75         @Override
unmarshal(ByteBuffer buffer)76         public T unmarshal(ByteBuffer buffer) {
77             int enumValue;
78 
79             switch (mNativeType) {
80                 case TYPE_INT32:
81                     enumValue = buffer.getInt();
82                     break;
83                 case TYPE_BYTE:
84                     // get the unsigned byte value; avoid sign extension
85                     enumValue = buffer.get() & UINT8_MASK;
86                     break;
87                 default:
88                     throw new AssertionError(
89                             "Unexpected native type; impossible since its not supported");
90             }
91 
92             return getEnumFromValue(mClass, enumValue);
93         }
94 
95         @Override
getNativeSize()96         public int getNativeSize() {
97             return getPrimitiveTypeSize(mNativeType);
98         }
99     }
100 
101     @Override
createMarshaler(TypeReference<T> managedType, int nativeType)102     public Marshaler<T> createMarshaler(TypeReference<T> managedType, int nativeType) {
103         return new MarshalerEnum(managedType, nativeType);
104     }
105 
106     @Override
isTypeMappingSupported(TypeReference<T> managedType, int nativeType)107     public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
108         if (nativeType == TYPE_INT32 || nativeType == TYPE_BYTE) {
109             if (managedType.getType() instanceof Class<?>) {
110                 Class<?> typeClass = (Class<?>)managedType.getType();
111 
112                 if (typeClass.isEnum()) {
113                     if (DEBUG) {
114                         Log.v(TAG, "possible enum detected for " + typeClass);
115                     }
116 
117                     // The enum must not take extra arguments
118                     try {
119                         // match a class like: "public enum Fruits { Apple, Orange; }"
120                         typeClass.getDeclaredConstructor(String.class, int.class);
121                         return true;
122                     } catch (NoSuchMethodException e) {
123                         // Skip: custom enum with a special constructor e.g. Foo(T), but need Foo()
124                         Log.e(TAG, "Can't marshal class " + typeClass + "; no default constructor");
125                     } catch (SecurityException e) {
126                         // Skip: wouldn't be able to touch the enum anyway
127                         Log.e(TAG, "Can't marshal class " + typeClass + "; not accessible");
128                     }
129                 }
130             }
131         }
132 
133         return false;
134     }
135 
136     @SuppressWarnings("rawtypes")
137     private static final HashMap<Class<? extends Enum>, int[]> sEnumValues =
138             new HashMap<Class<? extends Enum>, int[]>();
139 
140     /**
141      * Register a non-sequential set of values to be used with the marshal/unmarshal functions.
142      *
143      * <p>This enables get/set to correctly marshal the enum into a value that is C-compatible.</p>
144      *
145      * @param enumType The class for an enum
146      * @param values A list of values mapping to the ordinals of the enum
147      */
registerEnumValues(Class<T> enumType, int[] values)148     public static <T extends Enum<T>> void registerEnumValues(Class<T> enumType, int[] values) {
149         if (enumType.getEnumConstants().length != values.length) {
150             throw new IllegalArgumentException(
151                     "Expected values array to be the same size as the enumTypes values "
152                             + values.length + " for type " + enumType);
153         }
154         if (DEBUG) {
155             Log.v(TAG, "Registered enum values for type " + enumType + " values");
156         }
157 
158         sEnumValues.put(enumType, values);
159     }
160 
161     /**
162      * Get the numeric value from an enum.
163      *
164      * <p>This is usually the same as the ordinal value for
165      * enums that have fully sequential values, although for C-style enums the range of values
166      * may not map 1:1.</p>
167      *
168      * @param enumValue Enum instance
169      * @return Int guaranteed to be ABI-compatible with the C enum equivalent
170      */
getEnumValue(T enumValue)171     private static <T extends Enum<T>> int getEnumValue(T enumValue) {
172         int[] values;
173         values = sEnumValues.get(enumValue.getClass());
174 
175         int ordinal = enumValue.ordinal();
176         if (values != null) {
177             return values[ordinal];
178         }
179 
180         return ordinal;
181     }
182 
183     /**
184      * Finds the enum corresponding to it's numeric value. Opposite of {@link #getEnumValue} method.
185      *
186      * @param enumType Class of the enum we want to find
187      * @param value The numeric value of the enum
188      * @return An instance of the enum
189      */
getEnumFromValue(Class<T> enumType, int value)190     private static <T extends Enum<T>> T getEnumFromValue(Class<T> enumType, int value) {
191         int ordinal;
192 
193         int[] registeredValues = sEnumValues.get(enumType);
194         if (registeredValues != null) {
195             ordinal = -1;
196 
197             for (int i = 0; i < registeredValues.length; ++i) {
198                 if (registeredValues[i] == value) {
199                     ordinal = i;
200                     break;
201                 }
202             }
203         } else {
204             ordinal = value;
205         }
206 
207         T[] values = enumType.getEnumConstants();
208 
209         if (ordinal < 0 || ordinal >= values.length) {
210             throw new IllegalArgumentException(
211                     String.format(
212                             "Argument 'value' (%d) was not a valid enum value for type %s "
213                                     + "(registered? %b)",
214                             value,
215                             enumType, (registeredValues != null)));
216         }
217 
218         return values[ordinal];
219     }
220 }
221