001    /*
002     * To change this template, choose Tools | Templates
003     * and open the template in the editor.
004     */
005    
006    package org.bridj;
007    
008    import java.lang.reflect.Method;
009    import java.util.ArrayList;
010    import java.util.Iterator;
011    import java.util.List;
012    import java.util.Map;
013    import java.util.NoSuchElementException;
014    import java.util.WeakHashMap;
015    
016    /**
017     * Set of int-valued enum values that is itself int-valued (bitwise OR of all the values).<br>
018     * This helps use Java enums (that implement {@link ValuedEnum}) as combinable C flags (see {@link FlagSet#fromValues(Enum[]) fromValues(E...) }).
019     * @author ochafik
020     */
021    public class FlagSet<E extends Enum<E>> implements ValuedEnum<E> {
022        private final long value;
023        private final Class<E> enumClass;
024        private E[] enumClassValues;
025    
026        protected FlagSet(long value, Class<E> enumClass, E[] enumClassValues) {
027            this.enumClass = enumClass;
028            this.value = value;
029            this.enumClassValues = enumClassValues;
030        }
031    
032        private static Map<Class<?>, Object[]> enumsCache = new WeakHashMap<Class<?>, Object[]>();
033    
034        @SuppressWarnings("unchecked")
035            private static synchronized <EE extends Enum<EE>> EE[] getValues(Class<EE> enumClass) {
036            EE[] values = (EE[])enumsCache.get(enumClass);
037            if (values == null) {
038                try {
039                    Method valuesMethod = enumClass.getMethod("values");
040                    Class<?> valuesType = valuesMethod.getReturnType();
041                    if (!valuesType.isArray() || !ValuedEnum.class.isAssignableFrom(valuesType.getComponentType()))
042                        throw new RuntimeException();
043                    enumsCache.put(enumClass, values = (EE[])valuesMethod.invoke(null));
044                } catch (Exception ex) {
045                    throw new IllegalArgumentException("Class " + enumClass + " does not have a public static " + ValuedEnum.class.getName() + "[] values() method.", ex);
046                }
047            }
048            return (EE[])values;
049        }
050    
051        @Override
052        public boolean equals(Object o) {
053            if (!(o instanceof ValuedEnum))
054                return false;
055            return value() == ((ValuedEnum)o).value();
056        }
057    
058        @Override
059        public int hashCode() {
060            return ((Long)value()).hashCode();
061        }
062    
063        //@Override
064        public Iterator<E> iterator() {
065            return getMatchingEnums().iterator();
066        }
067        public E toEnum() {
068            E nullMatch = null;
069            E match = null;
070            for (E e : getMatchingEnums()) {
071                if (((ValuedEnum)e).value() == 0)
072                    nullMatch = e;
073                else if (match == null)
074                    match = e;
075                else
076                    throw new NoSuchElementException("More than one enum value corresponding to " + this + " : " + e + " and " + match + "...");
077            }
078            if (match != null)
079                return match;
080    
081            if (value() == 0)
082                return nullMatch;
083    
084            throw new NoSuchElementException("No enum value corresponding to " + this);
085        }
086    
087        @Override
088        public String toString() {
089            StringBuilder b = new StringBuilder();
090            b.append(enumClass.getSimpleName()).append("(").append(value()).append(" = ");
091            try {
092                boolean first = true;
093                for (E e : this.getMatchingEnums()) {
094                    if (first)
095                        first = false;
096                    else
097                        b.append(" | ");
098                    b.append(e);
099                }
100            } catch (Throwable th) {
101                b.append("?");
102            }
103            b.append(")");
104            return b.toString();
105        }
106    
107        public static <EE extends Enum<EE>> FlagSet<EE> fromValue(long value, Class<EE> enumClass) {
108            return new FlagSet<EE>(value, enumClass, null);
109        }
110        public static class IntFlagSet<E extends Enum<E>> extends FlagSet<E> implements IntValuedEnum<E> {
111            protected IntFlagSet(long value, Class<E> enumClass, E[] enumClassValues) {
112                    super(value, enumClass, enumClassValues);
113            }
114        }
115        public static <EE extends Enum<EE>> IntFlagSet<EE> fromValue(int value, Class<EE> enumClass) {
116            return new IntFlagSet<EE>(value, enumClass, null);
117        }
118        public static <EE extends Enum<EE>> FlagSet<EE> fromValue(ValuedEnum<EE> value) {
119            if (value instanceof Enum)
120                return FlagSet.fromValue(value.value(), (EE)value);
121            else 
122                return (FlagSet<EE>)value;
123        }
124        public static <EE extends Enum<EE>> FlagSet<EE> fromValue(long value, EE... enumValue) {
125            return new FlagSet<EE>(value, null, enumValue);
126        }
127        public static <EE extends Enum<EE>> IntFlagSet<EE> fromValue(int value, EE... enumValue) {
128            return new IntFlagSet<EE>(value, null, enumValue);
129        }
130        /**
131         * Isolate bits that are set in the value.<br>
132         * For instance, {@code getBits(0xf)} yields {@literal 0x1, 0x2, 0x4, 0x8}
133         * @param value
134         * @return split bits, which give the value back if OR-ed all together.
135         */
136        public static List<Long> getBits(final long value) {
137            List<Long> list = new ArrayList<Long>();
138            for (int i = 0; i < 64; i++) {
139                long bit = 1L << i;
140                if ((value & bit) != 0)
141                    list.add(bit);
142            }
143            return list;
144        }
145    
146        /**
147         * Get the integral value of this FlagSet.
148         * @return value of the flag set
149         */
150        //@Override
151        public long value() {
152            return value;
153        }
154    
155        public Class<E> getEnumClass() {
156            return enumClass;
157        }
158    
159        protected E[] getEnumClassValues() {
160            return enumClassValues == null ? enumClassValues = getValues(enumClass) : enumClassValues;
161        }
162        
163        /**
164         * Tests if the flagset value is equal to the OR combination of all the given values combined with bitwise OR operations.<br>
165         * The following C code :
166         * <pre>{@code
167         * E v = ...; // E is an enum type
168         * if (v == (E_V1 | E_V2)) { ... }
169         * }</pre>
170         * Can be translated to the following Java + BridJ code :
171         * <pre>{@code
172         * FlagSet<E> v = ...;
173         * if (v.is(E_V1, E_V2)) { ... }
174         * }</pre>
175         */
176        public boolean is(E... valuesToBeCombinedWithOR) {
177            return value() == orValue(valuesToBeCombinedWithOR);
178        }
179        
180        /**
181         * Tests if the flagset value is contains the OR combination of all the given values combined with bitwise OR operations.<br>
182         * The following C code :
183         * <pre>{@code
184         * E v = ...; // E is an enum type
185         * if (v & (E_V1 | E_V2)) { ... }
186         * }</pre>
187         * Can be translated to the following Java + BridJ code :
188         * <pre>{@code
189         * FlagSet<E> v = ...;
190         * if (v.has(E_V1, E_V2)) { ... }
191         * }</pre>
192         */
193        public boolean has(E... valuesToBeCombinedWithOR) {
194            return (value() & orValue(valuesToBeCombinedWithOR)) != 0;
195        }
196        
197        public FlagSet<E> or(E... valuesToBeCombinedWithOR) {
198            return new FlagSet(value() | orValue(valuesToBeCombinedWithOR), enumClass, null);
199        }
200        
201        static <E extends Enum<E>> long orValue(E... valuesToBeCombinedWithOR) {
202            long value = 0;
203            for (E v : valuesToBeCombinedWithOR)
204                    value |= ((ValuedEnum)v).value();
205            return value;
206        }
207        public FlagSet<E> without(E... valuesToBeCombinedWithOR) {
208            return new FlagSet(value() & ~orValue(valuesToBeCombinedWithOR), enumClass, null);
209        }
210        public FlagSet<E> and(E... valuesToBeCombinedWithOR) {
211            return new FlagSet(value() & orValue(valuesToBeCombinedWithOR), enumClass, null);
212        }
213    
214        protected List<E> getMatchingEnums() {
215            List<E> ret = new ArrayList<E>();
216            if (enumClass != null) {
217                for (E e : getEnumClassValues()) {
218                    long eMask = ((ValuedEnum<?>)e).value();
219                    if ((value & eMask) == eMask)
220                        ret.add((E)e);
221                }
222            }
223    
224            return ret;
225        }
226    
227            public static <E extends Enum<E>> FlagSet<E> fromValues(E... enumValues) {
228                    long value = 0;
229                    Class cl = null;
230                    for (E enumValue : enumValues) {
231                if (enumValue == null)
232                    continue;
233                            if (cl == null)
234                                    cl = enumValue.getClass();
235                            value |= ((ValuedEnum)enumValue).value();
236                    }
237                    return new FlagSet<E>(value, cl, enumValues);
238            }
239    
240    }