Sfoglia il codice sorgente

new: EnumFactory
new: ReflectUtils:rewritable

Mariusz Sieroń 7 anni fa
parent
commit
8500e00ce1

+ 67 - 0
assira/src/main/java/net/ranides/assira/generic/EnumFactory.java

@@ -0,0 +1,67 @@
+package net.ranides.assira.generic;
+
+import net.ranides.assira.collection.arrays.ArrayAllocator;
+import net.ranides.assira.collection.arrays.ArrayUtils;
+import net.ranides.assira.collection.maps.CacheMap;
+import net.ranides.assira.functional.VarFunction;
+import net.ranides.assira.reflection.IAttribute;
+import net.ranides.assira.reflection.IClass;
+import net.ranides.assira.reflection.IField;
+
+public final class EnumFactory<E extends Enum<E>> {
+
+    private static final String INCOMPATIBLE_JDK = "Incompatible JDK Version: can not create instances of enum at runtime";
+
+    @SuppressWarnings("unchecked")
+    private static final CacheMap<Class, EnumFactory> CACHE = CacheMap.getInstance(EnumFactory::new);
+
+    private final VarFunction<E> constructor;
+
+    private final IField values;
+
+    private EnumFactory(Class<E> type) {
+        IClass<E> typeinfo = IClass.typeinfo(type);
+
+        constructor = typeinfo.constructors()
+            .require(IAttribute.ANY)
+            .first()
+            .orElseThrow(() -> new UnsupportedOperationException(INCOMPATIBLE_JDK))
+            .accessor();
+
+        values = typeinfo.field("$VALUES")
+            .orElseThrow(() -> new UnsupportedOperationException(INCOMPATIBLE_JDK))
+            .rewritable();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <E extends Enum<E>> EnumFactory<E> forType(Class<E> type) {
+        return EnumFactory.CACHE.get(type);
+    }
+
+    public E newValue(String name, int ordinal) {
+        return constructor.call(name, ordinal);
+    }
+
+    public E newValue(String name, int ordinal, Object... args) {
+        return constructor.call(ArrayUtils.concat(new Object[]{name, ordinal}, args));
+    }
+
+    public EnumFactory<E> registerNewValue(String name, int ordinal) {
+        return register(newValue(name, ordinal));
+    }
+
+    public EnumFactory<E> registerNewValue(String name, int ordinal, Object... args) {
+        return register(newValue(name, ordinal, args));
+    }
+
+    public EnumFactory<E> register(E value) {
+        Object[] oldValues = (Object[])values.get(null);
+        Object[] newValues = ArrayAllocator.ensureCapacity(oldValues,value.ordinal()+1);
+        if(oldValues != newValues) {
+            values.set(null, newValues);
+        }
+        newValues[value.ordinal()] = value;
+        return this;
+    }
+
+}

+ 1 - 43
assira/src/main/java/net/ranides/assira/generic/EnumUtils.java

@@ -7,32 +7,17 @@
 
 package net.ranides.assira.generic;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.Map;
 import java.util.function.Function;
 import java.util.function.ToIntFunction;
 
-import net.ranides.assira.collection.arrays.ArrayUtils;
 import net.ranides.assira.collection.maps.IntMap;
 import net.ranides.assira.collection.maps.IntMapCollectors;
 import net.ranides.assira.collection.maps.MapCollectors;
-import net.ranides.assira.functional.VarFunction;
-import net.ranides.assira.reflection.IArguments;
-import net.ranides.assira.reflection.IAttribute;
-import net.ranides.assira.reflection.IClass;
-import net.ranides.assira.reflection.IField;
-import net.ranides.assira.reflection.IMethod;
-import net.ranides.assira.reflection.util.MethodUtils;
-import net.ranides.assira.reflection.util.ReflectUtils;
-import sun.misc.Unsafe;
-import sun.reflect.ConstructorAccessor;
 
 public final class EnumUtils {
 
-    private static final String INCOMPATIBLE_JDK = "Incompatible JDK Version: can not create instances of enum at runtime";
-
     private EnumUtils() {
         // utility class
     }
@@ -46,7 +31,7 @@ public final class EnumUtils {
     }
     
     public static <K,E extends Enum<E>> Map<E,K> apply(Class<E> eenum, Function<E,? extends K> function) {
-        return Arrays.stream(eenum.getEnumConstants()).collect(MapCollectors.unique(e -> e, e -> function.apply(e)));
+        return Arrays.stream(eenum.getEnumConstants()).collect(MapCollectors.unique(e -> e, function::apply));
     }
     
     public static <K,E extends Enum<E>> Map<E,K> apply(Class<E> eenum, K[] values) {
@@ -57,31 +42,4 @@ public final class EnumUtils {
         return Arrays.stream(keys).collect(MapCollectors.unique(e -> e, e -> values[e.ordinal()]));
     }
 
-    /**
-     * Create new instance of enumeration. Can be used to create enum values at runtime. Please note, that you can
-     * break switch/case statements if you decide to use synthetic enum value with unexpected ordinal value.
-     * @param type
-     * @param name
-     * @param ordinal
-     * @param <E>
-     * @return
-     */
-    public static <E extends Enum<E>> E newInstance(Class<E> type, String name, int ordinal) {
-        // @todo (assira #1) EnumUtils: finish experimental implementation
-        return accessor(type).call(name, ordinal);
-    }
-
-    public static <E extends Enum<E>> E newInstance(Class<E> type, String name, int ordinal, Object... args) {
-        return accessor(type).call(ArrayUtils.concat(new Object[]{name, ordinal}, args));
-    }
-
-    private static <E extends Enum<E>> VarFunction<E> accessor(Class<E> type) {
-        return IClass.typeinfo(type)
-            .constructors()
-            .require(IAttribute.ANY)
-            .first()
-            .orElseThrow(() -> new UnsupportedOperationException(INCOMPATIBLE_JDK))
-            .accessor();
-    }
-
 }

+ 2 - 0
assira/src/main/java/net/ranides/assira/reflection/IField.java

@@ -38,6 +38,8 @@ public interface IField extends IElement {
     Wrapper<Object> bind(Object that);
     
     Field reflective();
+
+    IField rewritable();
     
     MethodHandle getter();
     

+ 2 - 0
assira/src/main/java/net/ranides/assira/reflection/IFields.java

@@ -57,5 +57,7 @@ public interface IFields extends IElements<IField> {
     IClasses parent();
     
     CQuery<Field> reflective();
+
+    IFields rewritable();
     
 }

+ 7 - 1
assira/src/main/java/net/ranides/assira/reflection/impl/AFields.java

@@ -121,7 +121,13 @@ public class AFields extends AElements<IField> implements IFields {
     public CQuery<Field> reflective() {
         return stream().map(IField::reflective);
     }
-    
+
+    @Override
+    public IFields rewritable() {
+        each(IField::rewritable);
+        return this;
+    }
+
     @Override
     public IClasses parent() {
         return FElements.newClasses(AHints.EMPTY, stream(IField::parent));

+ 6 - 0
assira/src/main/java/net/ranides/assira/reflection/impl/RField.java

@@ -106,6 +106,12 @@ public class RField implements IField {
         return field;
     }
 
+    @Override
+    public IField rewritable() {
+        ReflectUtils.rewritable(field);
+        return this;
+    }
+
     @Override
     public MethodHandle getter() {
         return getter.get();

+ 49 - 24
assira/src/main/java/net/ranides/assira/reflection/util/ReflectUtils.java

@@ -6,25 +6,29 @@
  */
 package net.ranides.assira.reflection.util;
 
+import net.ranides.assira.io.PathUtils;
+import net.ranides.assira.system.RuntimeUtils;
+import net.ranides.assira.text.Charsets;
+import net.ranides.assira.text.IOStrings;
+import net.ranides.assira.trace.ExceptionUtils;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.nio.file.Path;
 import java.security.CodeSource;
-import net.ranides.assira.io.PathUtils;
-import net.ranides.assira.system.RuntimeUtils;
-import net.ranides.assira.text.Charsets;
-import net.ranides.assira.text.IOStrings;
 
 /**
  *
  * @author ranides
  */
 public final class ReflectUtils {
-    
+
     private ReflectUtils() { /* utility class */ }
-    
+
     /**
      * Próbuje wymusić prawa dostępu do podanego obiektu uzyskanego za pomocą refleksji
      * (np zyskać uprawnienia do wywołania prywatnej metody, albo odczytu prywatnego pola).
@@ -33,48 +37,69 @@ public final class ReflectUtils {
      * @return
      */
     public static <T extends AccessibleObject> T access(T object) {
-        if( object.isAccessible() ) { 
+        if( object.isAccessible() ) {
             return object;
         }
         try {
             RuntimeUtils.doPrivileged(() -> object.setAccessible(true));
-        } catch(SecurityException $0) { // NOPMD
-            // ignore
+        } catch(SecurityException cause) {
+            throw ExceptionUtils.rethrow(cause);
         }
         return object;
     }
-    
+
+    public static Field rewritable(Field field) {
+        if(0 == (field.getModifiers() & ~Modifier.FINAL)) {
+            return field;
+        }
+        try {
+            RuntimeUtils.doPrivileged(() -> {
+                try {
+                    field.setAccessible(true);
+                    Field modifiers = field.getClass().getDeclaredField("modifiers");
+                    modifiers.setAccessible(true);
+                    modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+                } catch (ReflectiveOperationException cause) {
+                    throw ExceptionUtils.rethrow(cause);
+                }
+            });
+        } catch(SecurityException cause) {
+            throw ExceptionUtils.rethrow(cause);
+        }
+        return field;
+    }
+
     /**
-     * Zwraca zawartość pliku źródłowego, w którym zapisana jest podana klasa. 
+     * Zwraca zawartość pliku źródłowego, w którym zapisana jest podana klasa.
      * Oczywiście, to nie działa idealnie dla klas, które nie mają pliku źródłowego
-     * w sensie stricte. Dla klas zagnieżdżonych albo anonimowych zwracana 
-     * jest cała zawartość, w którym je zapisano, a nie tylko fragment. 
+     * w sensie stricte. Dla klas zagnieżdżonych albo anonimowych zwracana
+     * jest cała zawartość, w którym je zapisano, a nie tylko fragment.
      * <p>
      * Kod źródłowy musi być dostępny dla ClassLoadera powiązanego z bieżącym wątkiem.
      * </p>
-     * 
+     *
      * @param classname
-     * @return 
+     * @return
      */
     public static String getSource(String classname) throws IOException {
         return readSourceCode(ClassUtils.loader(), classname);
     }
-    
+
     /**
-     * Zwraca zawartość pliku źródłowego, w którym zapisana jest podana klasa. 
+     * Zwraca zawartość pliku źródłowego, w którym zapisana jest podana klasa.
      * Oczywiście, to nie działa idealnie dla klas, które nie mają pliku źródłowego
-     * w sensie stricte. Dla klas zagnieżdżonych albo anonimowych zwracana 
-     * jest cała zawartość, w którym je zapisano, a nie tylko fragment. 
+     * w sensie stricte. Dla klas zagnieżdżonych albo anonimowych zwracana
+     * jest cała zawartość, w którym je zapisano, a nie tylko fragment.
      * <p>
      * Kod źródłowy musi być dostępny dla classloadera, który załadował klasę.
      * </p>
      * @param clazz
-     * @return 
+     * @return
      */
     public static String getSource(Class<?> clazz) throws IOException {
         return readSourceCode(clazz.getClassLoader(), clazz.getName());
     }
-    
+
     private static String readSourceCode(ClassLoader loader, String classname) throws IOException {
         String name = classname.replace('.', '/').replaceAll("\\$.+", "") + ".java";
         InputStream istream = loader.getResourceAsStream(name);
@@ -82,7 +107,7 @@ public final class ReflectUtils {
             return IOStrings.read(istream, Charsets.UTF8);
         }
 
-        File file;        
+        File file;
         if((file = new File(name)).exists()) {
             return IOStrings.read(file, Charsets.UTF8);
         }
@@ -94,7 +119,7 @@ public final class ReflectUtils {
         }
         throw new IOException("unknown location of file: " + name);
     }
-    
+
     /**
      * Zwraca lokalizację, w której został wyszukany odpowiedni plik .class z
      * definicją załadowanej klasy. Lokalizacja nie wskazuje na  konkretny
@@ -109,7 +134,7 @@ public final class ReflectUtils {
         }
         return PathUtils.fromURL(cs.getLocation());
     }
-    
+
     /**
      * Zwraca ścieżkę do pliku .class, z którego została załadowana klasa. Aby
      * uzyskać lokalizację, w której rozpoczęto wyszukiwanie pliku .class, należy

+ 46 - 0
assira/src/test/java/net/ranides/assira/generic/EnumFactoryTest.java

@@ -0,0 +1,46 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.generic;
+
+import java.util.Map;
+import net.ranides.assira.collection.maps.IntMap;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class EnumFactoryTest {
+
+    @Test
+    public void testNewInstance() {
+        EnumFactory<MyEnum> factory = EnumFactory.forType(MyEnum.class);
+        MyEnum value1 = factory.newValue("LAST", 6, 1024);
+        factory.register(value1);
+
+        assertEquals(value1.name(), "LAST");
+        assertEquals(value1.ordinal(), 6);
+        assertEquals(value1.v, 1024);
+        assertEquals(MyEnum.values().length, 7);
+    }
+
+    private enum MyEnum {
+        VALUE(1),
+        ITEM(4),
+        ENTRY(2),
+        CONST(8);
+        
+        private final int v;
+
+        MyEnum(int v) {
+            this.v = v;
+        }
+
+    }
+    
+}

+ 2 - 2
assira/src/test/java/net/ranides/assira/reflection/impl/RConstructorTest.java

@@ -135,13 +135,13 @@ public class RConstructorTest {
         VarFunction<Letters> aLetters = IClass.typeinfo(Letters.class).constructors()
             .require(IAttribute.DECLARED)
             .first()
-            .get()
+            .orElseThrow(() -> new AssertionError("Constructor not found: Letters"))
             .accessor();
 
         VarFunction<Cities> aCities = IClass.typeinfo(Cities.class).constructors()
             .require(IAttribute.DECLARED)
             .first()
-            .get()
+            .orElseThrow(() -> new AssertionError("Constructor not found: Cities"))
             .accessor();
 
         Letters letter1 = aLetters.call("Z", 26);