-
-
Save GotoFinal/2354ca1831aaaefc2a3a45bd71f7d636 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2017. Diorite (by Bartłomiej Mazur (aka GotoFinal)) | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
import java.lang.StackWalker.Option; | |
import java.lang.StackWalker.StackFrame; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Executable; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Modifier; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Objects; | |
import java.util.Set; | |
import java.util.function.Supplier; | |
import java.util.stream.Collectors; | |
/** | |
* Base class for creating dynamic enums, new elements can be added to dynamic enums, but not removed/changed. | |
* | |
* @param <T> | |
* type of enum. | |
*/ | |
@SuppressWarnings({"unchecked", "rawtypes"}) | |
public abstract class DynamicEnum<T extends DynamicEnum<T>> implements Comparable<T> { | |
private static final StackWalker walker = | |
StackWalker.getInstance(Set.of(Option.RETAIN_CLASS_REFERENCE, Option.SHOW_HIDDEN_FRAMES, Option.SHOW_REFLECT_FRAMES)); | |
int ordinal = - 1; | |
String name = "\0"; | |
private final transient Supplier<String> prettyName = new Supplier<String>() { | |
private String value; | |
@Override | |
public String get() { | |
if (this.value == null) { | |
this.value = DynamicEnum.this.createPrettyName(); | |
} | |
return this.value; | |
} | |
}; | |
@SuppressWarnings("StringEquality") | |
final void validate() { | |
if ((this.ordinal == - 1) || (this.name == "\0")) { | |
throw new IllegalStateException("Unregistered enum element: " + this.getClass() + ", " + this.name + ", " + this.ordinal); | |
} | |
} | |
protected DynamicEnum() { | |
Class<T> enumType = this.getDeclaringClass0(); | |
DynamicEnumType<T> dynamicEnumType = DynamicEnumType.getDynamicEnumType(enumType); | |
List<StackFrame> stackFrames = walker.walk(s -> s.skip(2).collect(Collectors.toList())); | |
List<StackFrame> validStack = new ArrayList<>(2); | |
boolean foundSpecial = false; | |
for (StackFrame stackFrame : stackFrames) { | |
if (foundSpecial) { | |
validStack.add(stackFrame); | |
if (validStack.size() == 2) { | |
break; | |
} | |
} | |
if (stackFrame.getDeclaringClass().equals(DynamicEnum.class) && stackFrame.getMethodName().equals("$")) { | |
foundSpecial = true; | |
} | |
} | |
if (! foundSpecial) { | |
for (StackFrame stackFrame : stackFrames) { | |
validStack.add(stackFrame); | |
if (validStack.size() == 2) { | |
break; | |
} | |
} | |
} | |
switch (ValueType.findType(validStack, enumType, this.getClass())) { | |
case DYNAMIC: | |
case DYNAMIC_EXTENDED: | |
return; | |
case NORMAL: | |
case EXTENDED: | |
this.ordinal = dynamicEnumType.counter++; | |
int index = 0; | |
for (Field field : enumType.getDeclaredFields()) { | |
if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && (field.getType() == enumType)) { | |
if (index++ == this.ordinal) { | |
this.name = field.getName(); | |
dynamicEnumType.addElement((T) this); | |
return; | |
} | |
} | |
} | |
throw new IllegalStateException("Can't find enum value declaration"); | |
} | |
} | |
protected static <T extends DynamicEnum<T>> T $(Object... arguments) { | |
StackFrame stackOne = walker.walk(s -> s.skip(1).limit(1).findFirst()).orElseThrow(InternalError::new); | |
if (! DynamicEnum.class.isAssignableFrom(stackOne.getDeclaringClass())) { | |
throw new InternalError("This method should be used only to create new instances of enum values!"); | |
} | |
try { | |
return findMatchingConstructor((Class<T>) stackOne.getDeclaringClass(), arguments).newInstance(arguments); | |
} | |
catch (RuntimeException e) { | |
if (e.getCause() instanceof InstantiationException) { | |
throw new InternalError("Can't create enum instance, there was error in constructor!", e.getCause()); | |
} | |
throw new InternalError("Exception when invoking constructor!", e); | |
} | |
catch (Exception e) { | |
throw new InternalError("Can't create enum instance using default constructor, $() method works only for simple constructor usages", e); | |
} | |
} | |
/** | |
* Returns ordinal of this enum element. | |
* | |
* @return ordinal of this enum element. | |
*/ | |
public final int ordinal() { | |
this.validate(); | |
return this.ordinal; | |
} | |
/** | |
* Returns name of this enum element. | |
* | |
* @return name of this enum element. | |
*/ | |
public final String name() { | |
this.validate(); | |
return this.name; | |
} | |
/** | |
* Returns name of this enum element in PascalCase and removed underscores. | |
* | |
* @return pretty name of this enum element. | |
*/ | |
public final String prettyName() { | |
return Objects.requireNonNull(this.prettyName.get()); | |
} | |
private String createPrettyName() { | |
String toLowerCase = this.name.toLowerCase(); | |
char[] charArray = toLowerCase.toCharArray(); | |
StringBuilder sb = new StringBuilder(toLowerCase.length()).append(Character.toUpperCase(charArray[0])); | |
char last = '\0'; | |
for (int i = 1; i < charArray.length; i++) { | |
char c = charArray[i]; | |
if (last == '_') { | |
sb.append(Character.toUpperCase(c)); | |
} | |
else if (c != '_') { | |
sb.append(c); | |
} | |
last = c; | |
} | |
return sb.toString(); | |
} | |
/** | |
* Returns the Class object corresponding to this enum constant's enum type. Two enum constants e1 and e2 are of the same enum type if and only if | |
* e1.getDeclaringClass() == e2.getDeclaringClass(). (The value returned by this method may differ from the one returned by the {@link Object#getClass} | |
* method for enum constants with constant-specific class bodies.) | |
* | |
* @return the Class object corresponding to this enum constant's enum type | |
*/ | |
public final Class<T> getDeclaringClass() { | |
this.validate(); | |
return this.getDeclaringClass0(); | |
} | |
private Class<T> getDeclaringClass0() { | |
Class<?> clazz = this.getClass(); | |
Class<?> superClass = clazz.getSuperclass(); | |
return (superClass.getSuperclass() == DynamicEnum.class) ? (Class<T>) superClass : (Class<T>) clazz; | |
} | |
/** | |
* Compares this enum with the specified object for order. Returns a | |
* negative integer, zero, or a positive integer as this object is less | |
* than, equal to, or greater than the specified object. | |
* | |
* Enum constants are only comparable to other enum constants of the | |
* same enum type. The natural order implemented by this | |
* method is the order in which the constants are declared. | |
*/ | |
@Override | |
public final int compareTo(T o) { | |
this.validate(); | |
DynamicEnum<T> self = this; | |
if ((self.getClass() != ((DynamicEnum<?>) o).getClass()) && // optimization | |
(self.getDeclaringClass() != ((DynamicEnum<?>) o).getDeclaringClass())) { | |
throw new ClassCastException(); | |
} | |
o.validate(); | |
return self.ordinal - o.ordinal; | |
} | |
/** | |
* Returns the name of this enum constant, as contained in the | |
* declaration. This method may be overridden, though it typically | |
* isn't necessary or desirable. An enum type should override this | |
* method when a more "programmer-friendly" string form exists. | |
* | |
* @return the name of this enum constant | |
*/ | |
public String toString() { | |
this.validate(); | |
return this.name; | |
} | |
/** | |
* Returns true if the specified object is equal to this | |
* enum constant. | |
* | |
* @param other | |
* the object to be compared for equality with this object. | |
* | |
* @return true if the specified object is equal to this enum constant. | |
*/ | |
public final boolean equals(Object other) { | |
this.validate(); | |
if (other instanceof DynamicEnum) { | |
((DynamicEnum) other).validate(); | |
return this == other; | |
} | |
else { | |
return false; | |
} | |
} | |
/** | |
* Returns a hash code for this enum constant. | |
* | |
* @return a hash code for this enum constant. | |
*/ | |
public final int hashCode() { | |
this.validate(); | |
return super.hashCode(); | |
} | |
/** | |
* Throws CloneNotSupportedException. This guarantees that enums | |
* are never cloned, which is necessary to preserve their "singleton" | |
* status. | |
* | |
* @return (never returns) | |
*/ | |
@Override | |
protected final Object clone() throws CloneNotSupportedException { | |
throw new CloneNotSupportedException(); | |
} | |
/** | |
* enum classes cannot have finalize methods. | |
*/ | |
@Override | |
protected final void finalize() {} | |
/** | |
* Returns array of values of given enum. | |
* | |
* @param enumType | |
* type of enum. | |
* @param <T> | |
* type og enum. | |
* | |
* @return array of values of given enum. | |
*/ | |
public static <T extends DynamicEnum<T>> T[] values(Class<T> enumType) { | |
return DynamicEnumType.getDynamicEnumType(enumType).values.clone(); | |
} | |
/** | |
* Adds new enum element to this dynamic enum. | |
* | |
* @param enumValue | |
* enum value. | |
* @param name | |
* name of enum value. | |
* @param <T> | |
* type of enum. | |
* | |
* @return ordinal value of enum. | |
*/ | |
public static <T extends DynamicEnum<T>> int addEnumElement(String name, T enumValue) { | |
Class<T> enumValueClass = (Class<T>) enumValue.getClass(); | |
while (enumValueClass.getSuperclass() != DynamicEnum.class) { | |
enumValueClass = (Class<T>) enumValueClass.getSuperclass(); | |
} | |
DynamicEnumType<T> dynamicEnumType = DynamicEnumType.getDynamicEnumType(enumValueClass); | |
dynamicEnumType.addElement(enumValue, name); | |
return enumValue.ordinal; | |
} | |
/** | |
* Returns the enum constant of the specified enum type with the | |
* specified name. | |
* The name must match exactly an identifier used | |
* to declare an enum constant in this type. | |
* | |
* @param <T> | |
* The enum type whose constant is to be returned | |
* @param enumType | |
* the {@code Class} object of the enum type from which to return a constant | |
* @param name | |
* the name of the constant to return | |
* | |
* @return the enum constant of the specified enum type with the specified name | |
* | |
* @exception IllegalArgumentException | |
* if the specified enum type has no constant with the specified name, or the specified class object does not represent an enum type | |
*/ | |
public static <T extends DynamicEnum<T>> T valueOf(Class<T> enumType, String name) { | |
T result = DynamicEnumType.getDynamicEnumType(enumType).elementsMap.get(name); | |
if (result != null) { | |
return result; | |
} | |
throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." + name); | |
} | |
/** | |
* Returns the enum constant of the specified enum type with the specified ordinal. | |
* | |
* @param <T> | |
* The enum type whose constant is to be returned | |
* @param enumType | |
* the {@code Class} object of the enum type from which to return a constant | |
* @param ordinal | |
* the name of the constant to return | |
* | |
* @return the enum constant of the specified enum type with the specified ordinal | |
* | |
* @exception IllegalArgumentException | |
* if the specified enum type has no constant with the specified ordinal, or the specified class object does not represent an enum type | |
*/ | |
public static <T extends DynamicEnum<T>> T valueOf(Class<T> enumType, int ordinal) { | |
T[] values = DynamicEnumType.getDynamicEnumType(enumType).values; | |
if ((ordinal >= 0) && (ordinal < values.length)) { | |
T result = values[ordinal]; | |
if (result != null) { | |
return result; | |
} | |
} | |
throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "#" + ordinal); | |
} | |
private static <T> Constructor<T> findMatchingConstructor(Class<T> type, Object... values) throws NoSuchMethodException, IllegalStateException { | |
return findMatchingExecutable((Constructor<T>[]) type.getDeclaredConstructors(), values); | |
} | |
private static <T> Constructor<T> findMatchingExecutable(Constructor<T>[] executables, | |
Object... values) throws NoSuchMethodException, IllegalStateException { | |
if (values.length == 0) { | |
for (Constructor<T> executable : executables) { | |
if (executable.getParameterCount() == 0) { | |
return setAccessible(executable); | |
} | |
} | |
throw new NoSuchMethodException("Can't find no-args constructor."); | |
} | |
Class<?>[] paramTypes = new Class<?>[values.length]; | |
for (int i = 0; i < values.length; i++) { | |
Object value = values[i]; | |
paramTypes[i] = (value == null) ? null : value.getClass(); | |
} | |
// try to find exact matching constructor, and add any just compatible to collection. | |
int exactMatches = 0; | |
Constructor<T> exact = null; | |
Constructor<T> bestMatch = null; | |
for (Constructor<T> executable : executables) { | |
if (executable.getParameterCount() != values.length) { | |
continue; | |
} | |
CompatibleExecutableResults compatibleConstructor = isCompatibleExecutable(executable, paramTypes); | |
if (compatibleConstructor == CompatibleExecutableResults.EXACT) { | |
if (exactMatches >= 1) { | |
throw new IllegalStateException("Ambiguous constructors found " + Arrays.toString(paramTypes)); | |
} | |
exact = executable; | |
exactMatches += 1; | |
} | |
if (compatibleConstructor != CompatibleExecutableResults.INVALID) { | |
bestMatch = getMoreSpecialized(bestMatch, executable); | |
} | |
} | |
if (bestMatch == null) { | |
throw new NoSuchMethodException("Can't find matching constructor for: " + Arrays.toString(paramTypes)); | |
} | |
if (exact != null) { | |
if (! bestMatch.equals(exact)) { | |
throw new IllegalStateException("Ambiguous constructors found " + Arrays.toString(paramTypes)); | |
} | |
return setAccessible(exact); | |
} | |
return setAccessible(bestMatch); | |
} | |
private static <T> Constructor<T> setAccessible(Constructor<T> executable) { | |
executable.setAccessible(true); | |
return executable; | |
} | |
private static <T> Constructor<T> getMoreSpecialized(Constructor<T> a, Constructor<T> b) { | |
if (a == null) { | |
return b; | |
} | |
if (b == null) { | |
return a; | |
} | |
Class<?>[] aTypes = a.getParameterTypes(); | |
Class<?>[] bTypes = b.getParameterTypes(); | |
int result = 0; | |
for (int i = 0; i < aTypes.length; i++) { | |
Class<?> aType = aTypes[i]; | |
Class<?> bType = bTypes[i]; | |
if (aType.equals(bType)) { | |
continue; | |
} | |
// if aType is less specialized than bType | |
if ((aType.isPrimitive() && ! bType.isPrimitive()) || | |
getWrapperClass(aType).isAssignableFrom(getWrapperClass(bType))) { | |
// one of prev types was less specialized, javac fails to find such constructor, we should too | |
if (result < 0) { | |
throw new IllegalStateException("Ambiguous constructors found for: " + Arrays.toString(aTypes) + " and " + Arrays.toString(bTypes)); | |
} | |
result += 1; | |
} | |
else { | |
if (result > 0) { | |
throw new IllegalStateException("Ambiguous constructors found for: " + Arrays.toString(aTypes) + " and " + Arrays.toString(bTypes)); | |
} | |
result -= 1; | |
} | |
} | |
if (result == 0) { | |
throw new IllegalStateException("Ambiguous constructors found for: " + Arrays.toString(aTypes) + " and " + Arrays.toString(bTypes)); | |
} | |
if (result < 0) { | |
return a; | |
} | |
return b; | |
} | |
private static CompatibleExecutableResults isCompatibleExecutable(Executable constructor, Class<?>[] providedTypes) { | |
Class<?>[] constructorParameterTypes = constructor.getParameterTypes(); | |
boolean compatible = true; | |
CompatibleExecutableResults current = CompatibleExecutableResults.EXACT; | |
for (int i = 0; i < constructorParameterTypes.length; i++) { | |
Class<?> providedType = providedTypes[i]; | |
Class<?> parameterType = constructorParameterTypes[i]; | |
// null can't be used as primitive | |
if ((providedType == null) && parameterType.isPrimitive()) { | |
return CompatibleExecutableResults.INVALID; | |
} | |
// handle primitives correctly by using wrapped type as boolean.class.isAssignableFrom(Boolean.class) => false | |
if ((providedType != null) && ! getWrapperClass(parameterType).isAssignableFrom(providedType)) { | |
return CompatibleExecutableResults.INVALID; | |
} | |
if ((providedType == null) || parameterType.equals(providedType)) { | |
continue; // sill exact match | |
} | |
current = CompatibleExecutableResults.COMPATIBLE; | |
} | |
return current; | |
} | |
private enum CompatibleExecutableResults { | |
EXACT, | |
COMPATIBLE, | |
INVALID | |
} | |
private static Class<?> getWrapperClass(Class<?> clazz) { | |
if (! clazz.isPrimitive()) { | |
return clazz; | |
} | |
if (clazz == boolean.class) { | |
return Boolean.class; | |
} | |
if (clazz == byte.class) { | |
return Byte.class; | |
} | |
if (clazz == short.class) { | |
return Short.class; | |
} | |
if (clazz == char.class) { | |
return Character.class; | |
} | |
if (clazz == int.class) { | |
return Integer.class; | |
} | |
if (clazz == long.class) { | |
return Long.class; | |
} | |
if (clazz == float.class) { | |
return Float.class; | |
} | |
if (clazz == double.class) { | |
return Double.class; | |
} | |
throw new Error("Unknown primitive type?"); // not possible? | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2017. Diorite (by Bartłomiej Mazur (aka GotoFinal)) | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
import org.junit.Assert; | |
import org.junit.Test; | |
public class DynamicEnumTest | |
{ | |
@Test | |
public void testEnum() | |
{ | |
Assert.assertSame(EnumExample.A, EnumExample.values()[0]); | |
Assert.assertSame(EnumExample.B, EnumExample.values()[1]); | |
Assert.assertSame(EnumExample.C, EnumExample.values()[2]); | |
Assert.assertSame(EnumExample.A, EnumExample.valueOf("A")); | |
EnumExample d = new EnumExample(); | |
EnumExample e = new EnumExample() {}; | |
Assert.assertSame(3, EnumExample.addEnumElement("D", d)); | |
Assert.assertSame(4, EnumExample.addEnumElement("E", e)); | |
Assert.assertSame(d, EnumExample.values()[3]); | |
Assert.assertSame(e, EnumExample.values()[4]); | |
Assert.assertSame(e, EnumExample.valueOf("E")); | |
// 0 -> A, 1 -> B, 2 -> C, 3 -> D, 4 -> E | |
for (EnumExample enumExample : EnumExample.values()) | |
{ | |
System.out.println(enumExample.ordinal() + " -> " + enumExample.name()); | |
} | |
} | |
static class EnumExample extends DynamicEnum<EnumExample> | |
{ | |
public static final EnumExample A = $(); | |
public static final EnumExample B = $(); | |
public static final EnumExample C = new EnumExample() | |
{ | |
public int doSomething() {return 7;} | |
}; | |
public int doSomething() {return 5;} | |
public static EnumExample[] values() {return DynamicEnum.values(EnumExample.class);} | |
public static EnumExample valueOf(String name) {return DynamicEnum.valueOf(EnumExample.class, name);} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2017. Diorite (by Bartłomiej Mazur (aka GotoFinal)) | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
import java.lang.reflect.Array; | |
import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.concurrent.ConcurrentHashMap; | |
final class DynamicEnumType<T extends DynamicEnum<T>> { | |
private static final Map<Class<?>, DynamicEnumType<?>> types = new ConcurrentHashMap<>(20); | |
final Map<String, T> elementsMap = Collections.synchronizedMap(new HashMap<>(10, 0.1f)); | |
private final Class<T> type; | |
T[] values; | |
volatile int counter; | |
@SuppressWarnings("unchecked") | |
DynamicEnumType(Class<T> type) { | |
this.type = type; | |
this.values = (T[]) Array.newInstance(type, 0); | |
} | |
synchronized void addElement(T element, String name) { | |
element.name = name; | |
element.ordinal = this.counter++; | |
this.addElement(element); | |
} | |
@SuppressWarnings("unchecked") | |
synchronized void addElement(T element) { | |
int oldLength = this.values.length; | |
if (element.ordinal != oldLength) { | |
throw new IllegalStateException("Expected enum element of id: " + oldLength + " but got: " + element.ordinal); | |
} | |
T[] newValues = (T[]) Array.newInstance(this.type, oldLength + 1); | |
System.arraycopy(this.values, 0, newValues, 0, oldLength); | |
newValues[oldLength] = element; | |
element.ordinal = oldLength; | |
this.values = newValues; | |
this.elementsMap.put(element.name, element); | |
} | |
@SuppressWarnings("unchecked") | |
static <T extends DynamicEnum<T>> DynamicEnumType<T> getDynamicEnumType(Class<T> type) { | |
DynamicEnumType<T> enumType = (DynamicEnumType<T>) types.get(type); | |
if (enumType != null) { | |
return enumType; | |
} | |
enumType = new DynamicEnumType<>(type); | |
types.putIfAbsent(type, enumType); | |
return enumType; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2017. Diorite (by Bartłomiej Mazur (aka GotoFinal)) | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
public class SampleEnum extends DynamicEnum<SampleEnum> | |
{ | |
public static final SampleEnum A = $("heh"); | |
private final String someProperty; | |
SampleEnum(String someProperty) {this.someProperty = someProperty;} | |
public String getSomeProperty() {return this.someProperty;} | |
public static SampleEnum[] values() {return DynamicEnum.values(SampleEnum.class);} | |
public static SampleEnum valueOf(String name) {return DynamicEnum.valueOf(SampleEnum.class, name);} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2017. Diorite (by Bartłomiej Mazur (aka GotoFinal)) | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
import java.lang.StackWalker.StackFrame; | |
import java.util.List; | |
enum ValueType | |
{ | |
NORMAL, | |
EXTENDED, | |
DYNAMIC, | |
DYNAMIC_EXTENDED; | |
static ValueType findType(List<StackFrame> frames, Class<?> enumType, Class<?> elementType) | |
{ | |
StackFrame stackOne = frames.get(0); | |
if (stackOne.getMethodName().equals("<clinit>") && (stackOne.getDeclaringClass() == enumType)) | |
{ | |
return ValueType.NORMAL; | |
} | |
if (frames.size() == 2) | |
{ | |
StackFrame stackTwo = frames.get(1); | |
if (stackOne.getMethodName().equals("<init>") && (stackOne.getDeclaringClass() == elementType) && stackTwo.getMethodName().equals("<clinit>") && | |
(stackTwo.getDeclaringClass() == enumType)) | |
{ | |
return ValueType.EXTENDED; | |
} | |
} | |
if (stackOne.getDeclaringClass().isAnonymousClass() && stackOne.getMethodName().equals("<init>") && (stackOne.getDeclaringClass() == elementType)) | |
{ | |
return ValueType.DYNAMIC_EXTENDED; | |
} | |
return ValueType.DYNAMIC; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment