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 */
16package android.hardware.camera2.marshal.impl;
17
18import android.hardware.camera2.marshal.Marshaler;
19import android.hardware.camera2.marshal.MarshalQueryable;
20import android.hardware.camera2.utils.TypeReference;
21import android.util.Log;
22
23import java.nio.ByteBuffer;
24import java.util.HashMap;
25
26import static android.hardware.camera2.impl.CameraMetadataNative.*;
27import 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 */
38public 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")
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
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
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
96        public int getNativeSize() {
97            return getPrimitiveTypeSize(mNativeType);
98        }
99    }
100
101    @Override
102    public Marshaler<T> createMarshaler(TypeReference<T> managedType, int nativeType) {
103        return new MarshalerEnum(managedType, nativeType);
104    }
105
106    @Override
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     */
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     */
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     */
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