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     @SuppressWarnings("ReturnValueIgnored")
107     @Override
isTypeMappingSupported(TypeReference<T> managedType, int nativeType)108     public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
109         if (nativeType == TYPE_INT32 || nativeType == TYPE_BYTE) {
110             if (managedType.getType() instanceof Class<?>) {
111                 Class<?> typeClass = (Class<?>)managedType.getType();
112 
113                 if (typeClass.isEnum()) {
114                     if (DEBUG) {
115                         Log.v(TAG, "possible enum detected for " + typeClass);
116                     }
117 
118                     // The enum must not take extra arguments
119                     try {
120                         // match a class like: "public enum Fruits { Apple, Orange; }"
121                         typeClass.getDeclaredConstructor(String.class, int.class);
122                         return true;
123                     } catch (NoSuchMethodException e) {
124                         // Skip: custom enum with a special constructor e.g. Foo(T), but need Foo()
125                         Log.e(TAG, "Can't marshal class " + typeClass + "; no default constructor");
126                     } catch (SecurityException e) {
127                         // Skip: wouldn't be able to touch the enum anyway
128                         Log.e(TAG, "Can't marshal class " + typeClass + "; not accessible");
129                     }
130                 }
131             }
132         }
133 
134         return false;
135     }
136 
137     @SuppressWarnings("rawtypes")
138     private static final HashMap<Class<? extends Enum>, int[]> sEnumValues =
139             new HashMap<Class<? extends Enum>, int[]>();
140 
141     /**
142      * Register a non-sequential set of values to be used with the marshal/unmarshal functions.
143      *
144      * <p>This enables get/set to correctly marshal the enum into a value that is C-compatible.</p>
145      *
146      * @param enumType The class for an enum
147      * @param values A list of values mapping to the ordinals of the enum
148      */
registerEnumValues(Class<T> enumType, int[] values)149     public static <T extends Enum<T>> void registerEnumValues(Class<T> enumType, int[] values) {
150         if (enumType.getEnumConstants().length != values.length) {
151             throw new IllegalArgumentException(
152                     "Expected values array to be the same size as the enumTypes values "
153                             + values.length + " for type " + enumType);
154         }
155         if (DEBUG) {
156             Log.v(TAG, "Registered enum values for type " + enumType + " values");
157         }
158 
159         sEnumValues.put(enumType, values);
160     }
161 
162     /**
163      * Get the numeric value from an enum.
164      *
165      * <p>This is usually the same as the ordinal value for
166      * enums that have fully sequential values, although for C-style enums the range of values
167      * may not map 1:1.</p>
168      *
169      * @param enumValue Enum instance
170      * @return Int guaranteed to be ABI-compatible with the C enum equivalent
171      */
getEnumValue(T enumValue)172     private static <T extends Enum<T>> int getEnumValue(T enumValue) {
173         int[] values;
174         values = sEnumValues.get(enumValue.getClass());
175 
176         int ordinal = enumValue.ordinal();
177         if (values != null) {
178             return values[ordinal];
179         }
180 
181         return ordinal;
182     }
183 
184     /**
185      * Finds the enum corresponding to it's numeric value. Opposite of {@link #getEnumValue} method.
186      *
187      * @param enumType Class of the enum we want to find
188      * @param value The numeric value of the enum
189      * @return An instance of the enum
190      */
getEnumFromValue(Class<T> enumType, int value)191     private static <T extends Enum<T>> T getEnumFromValue(Class<T> enumType, int value) {
192         int ordinal;
193 
194         int[] registeredValues = sEnumValues.get(enumType);
195         if (registeredValues != null) {
196             ordinal = -1;
197 
198             for (int i = 0; i < registeredValues.length; ++i) {
199                 if (registeredValues[i] == value) {
200                     ordinal = i;
201                     break;
202                 }
203             }
204         } else {
205             ordinal = value;
206         }
207 
208         T[] values = enumType.getEnumConstants();
209 
210         if (ordinal < 0 || ordinal >= values.length) {
211             throw new IllegalArgumentException(
212                     String.format(
213                             "Argument 'value' (%d) was not a valid enum value for type %s "
214                                     + "(registered? %b)",
215                             value,
216                             enumType, (registeredValues != null)));
217         }
218 
219         return values[ordinal];
220     }
221 }
222