001/*
002 * BridJ - Dynamic and blazing-fast native interop for Java.
003 * http://bridj.googlecode.com/
004 *
005 * Copyright (c) 2010-2013, Olivier Chafik (http://ochafik.com/)
006 * All rights reserved.
007 *
008 * Redistribution and use in source and binary forms, with or without
009 * modification, are permitted provided that the following conditions are met:
010 * 
011 *     * Redistributions of source code must retain the above copyright
012 *       notice, this list of conditions and the following disclaimer.
013 *     * Redistributions in binary form must reproduce the above copyright
014 *       notice, this list of conditions and the following disclaimer in the
015 *       documentation and/or other materials provided with the distribution.
016 *     * Neither the name of Olivier Chafik nor the
017 *       names of its contributors may be used to endorse or promote products
018 *       derived from this software without specific prior written permission.
019 * 
020 * THIS SOFTWARE IS PROVIDED BY OLIVIER CHAFIK AND CONTRIBUTORS ``AS IS'' AND ANY
021 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
022 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
023 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
024 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
025 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
026 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
027 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
029 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030 */
031package org.bridj;
032
033import java.lang.reflect.Array;
034import java.lang.reflect.Method;
035import java.util.ArrayList;
036import java.util.Collections;
037import java.util.Iterator;
038import java.util.List;
039import java.util.Map;
040import java.util.NoSuchElementException;
041import java.util.WeakHashMap;
042
043/**
044 * Set of int-valued enum values that is itself int-valued (bitwise OR of all
045 * the values).<br>
046 * This helps use Java enums (that implement {@link ValuedEnum}) as combinable C
047 * flags (see {@link FlagSet#fromValues(Enum[]) fromValues(E...) }).
048 *
049 * @author ochafik
050 */
051public class FlagSet<E extends Enum<E>> implements ValuedEnum<E> {
052
053    private final long value;
054    private final Class<E> enumClass;
055    private E[] enumClassValues;
056
057    protected FlagSet(long value, Class<E> enumClass, E[] enumClassValues) {
058        this.enumClass = enumClass;
059        this.value = value;
060        this.enumClassValues = enumClassValues;
061    }
062    private static Map<Class<?>, Object[]> enumsCache = new WeakHashMap<Class<?>, Object[]>();
063
064    @SuppressWarnings("unchecked")
065    private static synchronized <EE extends Enum<EE>> EE[] getValues(Class<EE> enumClass) {
066        EE[] values = (EE[]) enumsCache.get(enumClass);
067        if (values == null) {
068            try {
069                Method valuesMethod = enumClass.getMethod("values");
070                Class<?> valuesType = valuesMethod.getReturnType();
071                if (!valuesType.isArray() || !ValuedEnum.class.isAssignableFrom(valuesType.getComponentType())) {
072                    throw new RuntimeException();
073                }
074                enumsCache.put(enumClass, values = (EE[]) valuesMethod.invoke(null));
075            } catch (Exception ex) {
076                throw new IllegalArgumentException("Class " + enumClass + " does not have a public static " + ValuedEnum.class.getName() + "[] values() method.", ex);
077            }
078        }
079        return (EE[]) values;
080    }
081
082    @Override
083    public boolean equals(Object o) {
084        if (!(o instanceof ValuedEnum)) {
085            return false;
086        }
087        return value() == ((ValuedEnum) o).value();
088    }
089
090    @Override
091    public int hashCode() {
092        return ((Long) value()).hashCode();
093    }
094
095    //@Override
096    public Iterator<E> iterator() {
097        return getMatchingEnums().iterator();
098    }
099
100    public E toEnum() {
101        E nullMatch = null;
102        E match = null;
103        for (E e : getMatchingEnums()) {
104            if (((ValuedEnum) e).value() == 0) {
105                nullMatch = e;
106            } else if (match == null) {
107                match = e;
108            } else {
109                throw new NoSuchElementException("More than one enum value corresponding to " + this + " : " + e + " and " + match + "...");
110            }
111        }
112        if (match != null) {
113            return match;
114        }
115
116        if (value() == 0) {
117            return nullMatch;
118        }
119
120        throw new NoSuchElementException("No enum value corresponding to " + this);
121    }
122
123    @Override
124    public String toString() {
125        StringBuilder b = new StringBuilder();
126        b.append(enumClass.getSimpleName()).append("(").append(value()).append(" = ");
127        try {
128            boolean first = true;
129            for (E e : this.getMatchingEnums()) {
130                if (first) {
131                    first = false;
132                } else {
133                    b.append(" | ");
134                }
135                b.append(e);
136            }
137        } catch (Throwable th) {
138            b.append("?");
139        }
140        b.append(")");
141        return b.toString();
142    }
143
144    public static <EE extends Enum<EE>> FlagSet<EE> createFlagSet(long value, Class<EE> enumClass) {
145        return new FlagSet<EE>(value, enumClass, null);
146    }
147
148    public static class IntFlagSet<E extends Enum<E>> extends FlagSet<E> implements IntValuedEnum<E> {
149
150        protected IntFlagSet(long value, Class<E> enumClass, E[] enumClassValues) {
151            super(value, enumClass, enumClassValues);
152        }
153    }
154
155    public static <EE extends Enum<EE>> IntFlagSet<EE> createFlagSet(int value, Class<EE> enumClass) {
156        return new IntFlagSet<EE>(value, enumClass, null);
157    }
158
159    public static <EE extends Enum<EE>> FlagSet<EE> fromValue(ValuedEnum<EE> value) {
160        if (value instanceof Enum) {
161            return FlagSet.createFlagSet(value.value(), (EE) value);
162        } else {
163            return (FlagSet<EE>) value;
164        }
165    }
166
167    public static <EE extends Enum<EE>> FlagSet<EE> createFlagSet(long value, EE... enumValue) {
168        if (enumValue == null) {
169            throw new IllegalArgumentException("Expected at least one enum value");
170        }
171        Class<EE> enumClass = (Class) enumValue[0].getClass();
172        if (IntValuedEnum.class.isAssignableFrom(enumClass)) {
173            return new IntFlagSet<EE>(value, enumClass, enumValue);
174        } else {
175            return new FlagSet<EE>(value, enumClass, enumValue);
176        }
177    }
178//    public static <EE extends Enum<EE>> IntFlagSet<EE> createFlagSet(int value, EE... enumValue) {
179//        return (IntFlagSet<EE>)createFlagSet((long)value, enumValue);
180//    }
181
182    public static <EE extends Enum<EE>> IntValuedEnum<EE> fromValue(int value, Class<EE> enumClass) {
183        return (IntValuedEnum<EE>) fromValue((long) value, enumClass, enumClass.getEnumConstants());
184    }
185
186    public static <EE extends Enum<EE>> IntValuedEnum<EE> fromValue(int value, EE... enumValues) {
187        return (IntValuedEnum<EE>) fromValue((long) value, enumValues);
188    }
189
190    public static <EE extends Enum<EE>> ValuedEnum<EE> fromValue(long value, EE... enumValues) {
191        if (enumValues == null || enumValues.length == 0) {
192            throw new IllegalArgumentException("Expected at least one enum value");
193        }
194        Class<EE> enumClass = (Class) enumValues[0].getClass();
195        return fromValue(value, enumClass, enumValues);
196    }
197
198    protected static <EE extends Enum<EE>> ValuedEnum<EE> fromValue(long value, Class<EE> enumClass, EE... enumValue) {
199        List<EE> enums = getMatchingEnums(value, enumClass.getEnumConstants());
200        if (enums.size() == 1) {
201            return (ValuedEnum<EE>) enums.get(0);
202        }
203        if (IntValuedEnum.class.isAssignableFrom(enumClass)) {
204            return new IntFlagSet<EE>(value, enumClass, enums.toArray((EE[]) Array.newInstance(enumClass, enums.size())));
205        } else {
206            return new FlagSet<EE>(value, enumClass, enums.toArray((EE[]) Array.newInstance(enumClass, enums.size())));
207        }
208    }
209
210    /**
211     * Isolate bits that are set in the value.<br>
212     * For instance, {@code getBits(0xf)} yields {@literal 0x1, 0x2, 0x4, 0x8}
213     *
214     * @param value
215     * @return split bits, which give the value back if OR-ed all together.
216     */
217    public static List<Long> getBits(final long value) {
218        List<Long> list = new ArrayList<Long>();
219        for (int i = 0; i < 64; i++) {
220            long bit = 1L << i;
221            if ((value & bit) != 0) {
222                list.add(bit);
223            }
224        }
225        return list;
226    }
227
228    /**
229     * Get the integral value of this FlagSet.
230     *
231     * @return value of the flag set
232     */
233    //@Override
234    public long value() {
235        return value;
236    }
237
238    public Class<E> getEnumClass() {
239        return enumClass;
240    }
241
242    protected E[] getEnumClassValues() {
243        return enumClassValues == null ? enumClassValues = getValues(enumClass) : enumClassValues;
244    }
245
246    /**
247     * Tests if the flagset value is equal to the OR combination of all the
248     * given values combined with bitwise OR operations.<br>
249     * The following C code :
250     * <pre>{@code
251     * E v = ...; // E is an enum type
252     * if (v == (E_V1 | E_V2)) { ... }
253     * }</pre> Can be translated to the following Java + BridJ code :
254     * <pre>{@code
255     * FlagSet<E> v = ...;
256     * if (v.is(E_V1, E_V2)) { ... }
257     * }</pre>
258     */
259    public boolean is(E... valuesToBeCombinedWithOR) {
260        return value() == orValue(valuesToBeCombinedWithOR);
261    }
262
263    /**
264     * Tests if the flagset value is contains the OR combination of all the
265     * given values combined with bitwise OR operations.<br>
266     * The following C code :
267     * <pre>{@code
268     * E v = ...; // E is an enum type
269     * if (v & (E_V1 | E_V2)) { ... }
270     * }</pre> Can be translated to the following Java + BridJ code :
271     * <pre>{@code
272     * FlagSet<E> v = ...;
273     * if (v.has(E_V1, E_V2)) { ... }
274     * }</pre>
275     */
276    public boolean has(E... valuesToBeCombinedWithOR) {
277        return (value() & orValue(valuesToBeCombinedWithOR)) != 0;
278    }
279
280    public FlagSet<E> or(E... valuesToBeCombinedWithOR) {
281        return new FlagSet(value() | orValue(valuesToBeCombinedWithOR), enumClass, null);
282    }
283
284    static <E extends Enum<E>> long orValue(E... valuesToBeCombinedWithOR) {
285        long value = 0;
286        for (E v : valuesToBeCombinedWithOR) {
287            value |= ((ValuedEnum) v).value();
288        }
289        return value;
290    }
291
292    public FlagSet<E> without(E... valuesToBeCombinedWithOR) {
293        return new FlagSet(value() & ~orValue(valuesToBeCombinedWithOR), enumClass, null);
294    }
295
296    public FlagSet<E> and(E... valuesToBeCombinedWithOR) {
297        return new FlagSet(value() & orValue(valuesToBeCombinedWithOR), enumClass, null);
298    }
299
300    protected List<E> getMatchingEnums() {
301        return enumClass == null ? Collections.EMPTY_LIST : getMatchingEnums(value, getEnumClassValues());
302    }
303
304    protected static <EE extends Enum<EE>> List<EE> getMatchingEnums(long value, EE[] enums) {
305        List<EE> ret = new ArrayList<EE>();
306        for (EE e : enums) {
307            long eMask = ((ValuedEnum<?>) e).value();
308            if ((eMask == 0 && value == 0) || (eMask != 0 && (value & eMask) == eMask)) {
309                ret.add((EE) e);
310            }
311        }
312        return ret;
313    }
314
315    public static <E extends Enum<E>> FlagSet<E> fromValues(E... enumValues) {
316        long value = 0;
317        Class cl = null;
318        for (E enumValue : enumValues) {
319            if (enumValue == null) {
320                continue;
321            }
322            if (cl == null) {
323                cl = enumValue.getClass();
324            }
325            value |= ((ValuedEnum) enumValue).value();
326        }
327        return new FlagSet<E>(value, cl, enumValues);
328    }
329}