2017-10-08

5: How to Make Constant Names and Values of Any Constants Group Enumerable

<The previous article in this series | The table of contents of this series | The next article in this series>

Main Body START

Let's Make a Base Class of Constants Groups Whose Constant Names or Values Have to Be Enumerable

About: Java Programming Language

Why We Don't Just Use Enums

-Hypothesizer

We talked about an limitation of enums in a previous article.

-Rebutter

We can't extend any enum, whether it is a Java Enum or an enum that we created ourselves, without ruining the essence of enums.

-Hypothesizer

The essence of enums, at least for us, is to restrict an argument of a method to a set of predetermined values. If we don't need the restriction, we can just use constants groups. Then we can extend them as we like.

-Rebutter

What we mean by 'constants group' is typically an interface like this.

@Java Source Code
public interface AConstantsGroup {
 String c_value1 = "Value1";
 String c_value2 = "Value2";
}

-Hypothesizer

Yes. Although we could make it a class, then, we wouldn't be able to use multiple inheritance. So, if there is no particularly reason, we will make it an interface.

-Rebutter

Ah-ha.

-Hypothesizer

Certainly, an enum is handy when we create a method that receives the enum. As the argument type is specific, not just String or something, it is clear what we can pass to the argument, and it is guaranteed that only predetermined values will be passed to the argument, without any necessity to do tests.

However, if we are passing a value to, say, a String argument of an existing method (for example, a method of the JDK standard library), there is not much merit in using an enum.

-Rebutter

I guess, you are talking about a case in which we don't want to use literals scattered in calling the method in many places.

-Hypothesizer

The method itself doesn't restrict the argument to finite options of possible values, but our program uses only a finite set of values, and we want to manage the set as a constants group.

-Rebutter

OK.

-Hypothesizer

If we use an enum in such a case, we will needlessly assume a handicap of not being able to extend it. In fact, sometimes, we want to extend it.

-Rebutter

So, we use constants groups in such cases.

-Hypothesizer

However, in some cases, we want to enumerate the values or the names of the constants contained in the constants group.

-Rebutter

That, we can't do with simple interfaces like the above one.

-Hypothesizer

So, here, we will make that possible.

What Is Our Design?

-Hypothesizer

We will create a base abstract class that has the functionality of collecting the names and the values of the constants contained in the class that extends it.

In fact, we create the class, 'abstract public class BaseEnumerableConstantsGroup <T>' (in the package, 'thebiasplanet.coreutilities.constantsgroups').

-Rebutter

What is 'T'?

-Hypothesizer

It's the class of the constant values.

-Rebutter

I see.

-Hypothesizer

We use the base class like this.

@Java Source Code
package test.enumerableconstantsgrouptest1;

public interface Test1ConstantsGroup1 {
 String c_value1 = "Value1";
 String c_value2 = "Value2";
}

package test.enumerableconstantsgrouptest1;

import thebiasplanet.coreutilities.constantsgroups.BaseEnumerableConstantsGroup;

public final class Test1EnumerableConstantsGroup1 extends BaseEnumerableConstantsGroup <String> implements Test1ConstantsGroup1 {
 public static final Test1EnumerableConstantsGroup1 c_instance = new Test1EnumerableConstantsGroup1 ();
 
 private Test1EnumerableConstantsGroup1 () {
 }
}

-Rebutter

We create the constants group, 'Test1ConstantsGroup1', as an interface, as usual, then create the enumerable constants group as a concrete class of the base class, implementing the constants group.

-Hypothesizer

Yes. We don't extend enumerable constants groups, but extend constants groups as interfaces and make enumerable versions of those constants groups. That way, we can use multiple inheritance.

This is an example.

@Java Source Code
package test.enumerableconstantsgrouptest1;

public interface Test1ConstantsGroup2 {
 String c_value3 = "Value3";
 String c_value4 = "Value4";
}

package test.enumerableconstantsgrouptest1;

public interface Test1ExtendedConstantsGroup12 extends Test1ConstantsGroup1, Test1ConstantsGroup2 {
 String c_value1 = "Value1 overwritten";
 String c_value4 = "Value4 overwritten";
 String c_value5 = "Value5";
 String c_value6 = "Value6";
}

package test.enumerableconstantsgrouptest1;

import thebiasplanet.coreutilities.constantsgroups.BaseEnumerableConstantsGroup;

public final class Test1EnumerableExtendedConstantsGroup12 extends BaseEnumerableConstantsGroup <String> implements Test1ExtendedConstantsGroup12 {
 public static final Test1EnumerableExtendedConstantsGroup12 c_instance = new Test1EnumerableExtendedConstantsGroup12 ();
 
 private Test1EnumerableExtendedConstantsGroup12 () {
 }
}

-Rebutter

Well, what the concrete class has to do is to extend the abstract class, implement the interface, define the singleton instance, and make the constructor private.

-Hypothesizer

Yes. I hope, that's not too much.

-Rebutter

How do we get the names and the values of the constants?

-Hypothesizer

We can get them like this.

@Java Source Code
  LinkedHashSet <String> l_names = Test1EnumerableExtendedConstantsGroup12.c_instance.getNames ();
  ArrayList <String> l_values = Test1EnumerableExtendedConstantsGroup12.c_instance.getValues ();

What Is Our Implementation?

-Rebutter

How do we implement it?

-Hypothesizer

The base class has the field that contains the constant names and values as a name-to-value LinkedHashMap; the constructor collects the names and the values of the constants and store them into the field; the public methods, 'getNames' and 'getValues' disclose the names and the values respectively.

-Rebutter

How do we collect the names and the values?

-Hypothesizer

We use the reflection feature of Java.

-Rebutter

Hmm, using the reflection feature can be a source of performance concerns, . . .

-Hypothesizer

That's the reason we have to make it a singleton. As it is a singleton, the initialization will, basically, run only once, and won't be, practically, a performance concern.

-Rebutter

Well, then, probably, there will be no performance problem.

-Hypothesizer

We get the names and the values of the constants from 'this' in the constructor of 'BaseEnumerableConstantsGroup', but we can't use the 'getFields' method of the class, java.lang.Class.

-Rebutter

Why not?

-Hypothesizer

Because the method gets also shadowed fields.

-Rebutter

So, the method gets not only 'Test1ExtendedConstantsGroup12.c_value1', but also 'Test1ConstantsGroup1.c_value1'?

-Hypothesizer

Yes. Besides, we don't like the order of the fields in which the method returns them: the fields of the sub interface come first.

-Rebutter

So, in the example above, we would get the fields in this order.

  • 'Test1ExtendedConstantsGroup12.c_value1'
  • 'Test1ExtendedConstantsGroup12.c_value4'
  • 'Test1ExtendedConstantsGroup12.c_value5'
  • 'Test1ExtendedConstantsGroup12.c_value6'
  • 'Test1ConstantsGroup1.c_value1'
  • 'Test1ConstantsGroup1.c_value2'
  • 'Test1ConstantsGroup2.c_value3'
  • 'Test1ConstantsGroup2.c_value4'
-Hypothesizer

We want them in this order.

  • 'Test1ExtendedConstantsGroup12.c_value1'
  • 'Test1ConstantsGroup1.c_value2'
  • 'Test1ConstantsGroup2.c_value3'
  • 'Test1ExtendedConstantsGroup12.c_value4'
  • 'Test1ExtendedConstantsGroup12.c_value5'
  • 'Test1ExtendedConstantsGroup12.c_value6'
-Rebutter

So, the fields of the super interface come first, but when a field is overwritten, the field will be replaced at the original position.

-Hypothesizer

That's what we want.

So, I created a static method in a utility class, 'thebiasplanet.coreutilities.reflectionshandling.ReflectionHandler', that fulfills our requirement. The signature of the method is this.

@Java Source Code
 public static LinkedHashMap <String, Object> getFieldNamesAndValues (Class <?> a_class, Object a_instance, boolean a_publicOnly, boolean a_setAccessible, List <Class <?>> a_assignableClassesOfFields, List <Class <?>> a_notAssignableClassesOfFields, boolean a_recursive) throws IllegalAccessException

-Rebutter

What is 'a_setAccessible'?

-Hypothesizer

If the argument is set 'true', the method tries to get also fields that are invisible from the caller (for example 'private' fields). However, it isn't guaranteed to be able to do so: it depends on the security configuration.

-Rebutter

In this case, we don't need to set it 'true' because all the fields are 'public'.

-Hypothesizer

The method has the argument because I mean it to be for general purposes, not only for this case.

-Rebutter

What are 'a_assignableClassesOfFields' and 'a_notAssignableClassesOfFields'?

-Hypothesizer

They are for selecting fields to be gotten: only fields that are assignable to a class in 'a_assignableClassesOfFields' and are not assignable to any class in 'a_notAssignableClassesOfFields' are gotten. Such a selection is necessary because, otherwise, the field, 'c_instance', would be needlessly gotten.

-Rebutter

Ah-ha.

I presume that 'a_recursive' specifies whether the method will search fields recursively into super classes, super interfaces, and implemented interfaces.

-Hypothesizer

Yes.

We call the method in the constructor of 'BaseEnumerableConstantsGroup' like this.

@Java Source Code
   i_nameToValueMap = ReflectionHandler.getFieldNamesAndValues (this.getClass (), null, true, false, null, ListFactory. <Class <?>>createArrayList (this.getClass ()), true);

-Rebutter

What is 'ListFactory'?

-Hypothesizer

Ah, that's just another utility class that creates List instances: I created it because initiating a List isn't as handy as initiating an array.

-Rebutter

We seem to have passed 'null' to 'a_instance' because the instance isn't necessary when we get only static fields.

-Hypothesizer

Yes. Passing 'null' to 'a_instance' means getting only static fields.

Here Are the Codes

-Hypothesizer

Here, I cite the code of those utility classes and 'BaseEnumerableConstantsGroup'.

@Java Source Code
package thebiasplanet.coreutilities.collectionshandling;

import java.util.ArrayList;
import java.util.Arrays;

public class ListFactory {
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T> ArrayList <T> createArrayList (Object ... a_items) {
  ArrayList <T> l_arrayList = new ArrayList <T> ();
  if (a_items != null) {
   Arrays.stream (a_items).forEach (a_item -> {
    l_arrayList.add ( (T) a_item);
   });
  }
  return l_arrayList;
 }
 
 // This doesn't accept any array as an expandable item; pass Iterables instead.
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T> ArrayList <T> createArrayListExpandingItems (Object ... a_items) {
  ArrayList <T> l_arrayList = new ArrayList <T> ();
  if (a_items != null) {
   Arrays.stream (a_items).forEach (a_item -> {
    if (a_item instanceof Iterable) {
     for (Object l_element: (Iterable) a_item) {
      l_arrayList.add ( (T) l_element);
     }
    }
    else {
     l_arrayList.add ( (T) a_item);
    }
   });
  }
  return l_arrayList;
 }
}

package thebiasplanet.coreutilities.collectionshandling;

import java.util.LinkedHashSet;
import java.util.Arrays;

public class SetFactory {
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T> LinkedHashSet <T> createLinkedHashSet (Object ... a_items) {
  LinkedHashSet <T> l_linkedHashSet = new LinkedHashSet <T> ();
  if (a_items != null) {
   Arrays.stream (a_items).forEach (a_item -> {
    l_linkedHashSet.add ( (T) a_item);
   });
  }
  return l_linkedHashSet;
 }
 
 // This doesn't accept any array as an expandable item; pass Iterables instead.
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T> LinkedHashSet <T> createLinkedHashSetExpandingItems (Object ... a_items) {
  LinkedHashSet <T> l_linkedHashSet = new LinkedHashSet <T> ();
  if (a_items != null) {
   Arrays.stream (a_items).forEach (a_item -> {
    if (a_item instanceof Iterable) {
     for (Object l_element: (Iterable) a_item) {
      l_linkedHashSet.add ( (T) l_element);
     }
    }
    else {
     l_linkedHashSet.add ( (T) a_item);
    }
   });
  }
  return l_linkedHashSet;
 }
}

package thebiasplanet.coreutilities.collectionshandling;

import java.util.Map;
import java.util.LinkedHashMap;

public class MapFactory {
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T, U> LinkedHashMap <T, U> createLinkedHashMap (Object ... a_alternatelyKeyAndValue) {
  LinkedHashMap <T, U> l_linkedHashMap = new LinkedHashMap <T, U> ();
  if (a_alternatelyKeyAndValue != null) {
   T l_key = null;
   for (Object l_keyOrValue: a_alternatelyKeyAndValue) {
    if (l_key == null) {
     l_key = (T) l_keyOrValue;
    }
    else {
     l_linkedHashMap.put (l_key, (U) l_keyOrValue);
     l_key = null;
    }
   }
  }
  return l_linkedHashMap;
 }
 
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T, U> LinkedHashMap <T, U> createLinkedHashMapExpandingItems (Object ... a_alternatelyKeyAndValueOrMaps) {
  LinkedHashMap <T, U> l_linkedHashMap = new LinkedHashMap <T, U> ();
  if (a_alternatelyKeyAndValueOrMaps != null) {
   T l_key = null;
   for (Object l_keyOrValueOrMap: a_alternatelyKeyAndValueOrMaps) {
    if (l_keyOrValueOrMap instanceof Map) {
     for (Map.Entry <?, ?> l_mapEntry: ((Map <?, ?>) l_keyOrValueOrMap).entrySet ()) {
      l_linkedHashMap.put ((T) l_mapEntry.getKey (), (U) l_mapEntry.getValue ());
      l_key = null;
     }
    }
    else {
     if (l_key == null) {
      l_key = (T) l_keyOrValueOrMap;
     }
     else {
      l_linkedHashMap.put (l_key, (U) l_keyOrValueOrMap);
      l_key = null;
     }
    }
   }
  }
  return l_linkedHashMap;
 }
}

package thebiasplanet.coreutilities.reflectionshandling;

import java.util.List;
import java.util.LinkedHashMap;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class ReflectionHandler {
 // Fields are stored in the order of from the super class to the sub class and from the implemented interfaces to the implementing class.
 // Duplicate named fields are replaced by the sub class's.
 // If all the gotten fields are static, set a_instance to be null.
 public static LinkedHashMap <String, Object> getFieldNamesAndValues (Class <?> a_class, Object a_instance, boolean a_publicOnly, boolean a_setAccessible, List <Class <?>> a_assignableClassesOfFields, List <Class <?>> a_notAssignableClassesOfFields, boolean a_recursive) throws IllegalAccessException {
  LinkedHashMap <String, Object> l_fieldNameToValueMap = new LinkedHashMap <String, Object> ();
  if (a_recursive) {
   LinkedHashMap <String, Object> l_superClassOrImplementedInterfaceFieldNameToValueMap = null;
   Class<?> l_superClass = a_class.getSuperclass();
   if (l_superClass != null) {
    l_superClassOrImplementedInterfaceFieldNameToValueMap = getFieldNamesAndValues (l_superClass, a_instance, a_publicOnly, a_setAccessible, a_assignableClassesOfFields, a_notAssignableClassesOfFields, true);
    l_fieldNameToValueMap.putAll (l_superClassOrImplementedInterfaceFieldNameToValueMap);
   }
   Class<?>[] l_implementedInterfaces = null;
   l_implementedInterfaces = a_class.getInterfaces();
   for (Class<?> l_implementedInterface: l_implementedInterfaces) {
    l_superClassOrImplementedInterfaceFieldNameToValueMap = getFieldNamesAndValues (l_implementedInterface, null, a_publicOnly, a_setAccessible, a_assignableClassesOfFields, a_notAssignableClassesOfFields, true);
    l_fieldNameToValueMap.putAll (l_superClassOrImplementedInterfaceFieldNameToValueMap);
   }
  }
  Field [] l_fields = l_fields = a_class.getDeclaredFields ();
  FieldsLoop: for (Field l_field: l_fields) {
   int l_fieldModifiers = l_field.getModifiers ();
   if (a_publicOnly && !Modifier.isPublic (l_fieldModifiers)) {
    continue;
   }
   if (a_instance == null && !Modifier.isStatic (l_fieldModifiers)) {
    continue;
   }
   if (a_assignableClassesOfFields != null) {
    boolean l_assignable = false;
    for (Class <?> l_assignableClassOfFields: a_assignableClassesOfFields) {
     if (!l_assignableClassOfFields.isAssignableFrom (l_field.getType ())) {
      l_assignable = true;
      break;
     }
    }
    if (!l_assignable) {
     continue;
    }
   }
   if (a_notAssignableClassesOfFields != null) {
    for (Class <?> l_notAssignableClassOfFields: a_notAssignableClassesOfFields) {
     if (l_notAssignableClassOfFields.isAssignableFrom (l_field.getType ())) {
      continue FieldsLoop;
     }
    }
   }
   boolean l_isAccessible = l_field.isAccessible ();
   if (a_setAccessible && !l_isAccessible) {
    l_field.setAccessible (true);
   }
   l_fieldNameToValueMap.put (l_field.getName (), l_field.get (a_instance));
   if (a_setAccessible && !l_isAccessible) {
    l_field.setAccessible (l_isAccessible);
   }
  }
  return l_fieldNameToValueMap;
 }
}

package thebiasplanet.coreutilities.constantsgroups;

// ## Comments that start with the mark, ##, are instructions for extending this class, where XXX is the class name of the extended class.
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import thebiasplanet.coreutilities.reflectionshandling.ReflectionHandler;
import thebiasplanet.coreutilities.collectionshandling.ListFactory;
import thebiasplanet.coreutilities.collectionshandling.SetFactory;
import thebiasplanet.coreutilities.collectionshandling.MapFactory;

// ## The class has to be public, extend BaseConstantsGroup, and implement some constants group interfaces of T constants
abstract public class BaseEnumerableConstantsGroup <T> {
 private LinkedHashMap <String, T> i_nameToValueMap;
 // ## Define this.
 // ## public static final XXX c_instance = new XXX ();
 
 // ## Define the private constructor.
 
 protected BaseEnumerableConstantsGroup () {
  try {
   i_nameToValueMap = MapFactory. <String, T>createLinkedHashMapExpandingItems (ReflectionHandler.getFieldNamesAndValues (this.getClass (), null, true, false, null, ListFactory. <Class <?>>createArrayList (this.getClass ()), true));
  }
  catch (IllegalAccessException l_exception) {
   throw new RuntimeException (l_exception);
  }
 }
 
 public LinkedHashSet <String> getNames () {
  return SetFactory. <String>createLinkedHashSetExpandingItems (i_nameToValueMap.keySet ());
 }
 
 public ArrayList <T> getValues () {
  return ListFactory. <T>createArrayListExpandingItems (i_nameToValueMap.values ());
 }
}

In fact, the source files (including ones for the test) are here. Those projects use Gradle or Ant. Descriptions about how to build the developing environment are here for Linux or here for Windows (ignore descriptions about what are irrelevant here). Expanding the zip file with the directories structure preserved, changing the current directory to each project, and executing the command 'gradle' or 'ant' should build the project. To do the test, changing to the 'coreUtilitiesTestToDisclose' directory and executing the command, 'gradle test' or 'ant test' should do.

Main Body END

<The previous article in this series | The table of contents of this series | The next article in this series>