I often use this design in my code to maintain configurable values. Consider this code:
public enum Options {
REGEX_STRING("Some Regex"),
REGEX_PATTERN(Pattern.compile(REGEX_STRING.getString()), false),
THREAD_COUNT(2),
OPTIONS_PATH("options.config", false),
DEBUG(true),
ALWAYS_SAVE_OPTIONS(true),
THREAD_WAIT_MILLIS(1000);
Object value;
boolean saveValue = true;
private Options(Object value) {
this.value = value;
}
private Options(Object value, boolean saveValue) {
this.value = value;
this.saveValue = saveValue;
}
public void setValue(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
public String getString() {
return value.toString();
}
public boolean getBoolean() {
Boolean booleanValue = (value instanceof Boolean) ? (Boolean) value : null;
if (value == null) {
try {
booleanValue = Boolean.valueOf(value.toString());
}
catch (Throwable t) {
}
}
// We want a NullPointerException here
return booleanValue.booleanValue();
}
public int getInteger() {
Integer integerValue = (value instanceof Number) ? ((Number) value).intValue() : null;
if (integerValue == null) {
try {
integerValue = Integer.valueOf(value.toString());
}
catch (Throwable t) {
}
}
return integerValue.intValue();
}
public float getFloat() {
Float floatValue = (value instanceof Number) ? ((Number) value).floatValue() : null;
if (floatValue == null) {
try {
floatValue = Float.valueOf(value.toString());
}
catch (Throwable t) {
}
}
return floatValue.floatValue();
}
public static void saveToFile(String path) throws IOException {
FileWriter fw = new FileWriter(path);
Properties properties = new Properties();
for (Options option : Options.values()) {
if (option.saveValue) {
properties.setProperty(option.name(), option.getString());
}
}
if (DEBUG.getBoolean()) {
properties.list(System.out);
}
properties.store(fw, null);
}
public static void loadFromFile(String path) throws IOException {
FileReader fr = new FileReader(path);
Properties properties = new Properties();
properties.load(fr);
if (DEBUG.getBoolean()) {
properties.list(System.out);
}
Object value = null;
for (Options option : Options.values()) {
if (option.saveValue) {
Class<?> clazz = option.value.getClass();
try {
if (String.class.equals(clazz)) {
value = properties.getProperty(option.name());
}
else {
value = clazz.getConstructor(String.class).newInstance(properties.getProperty(option.name()));
}
}
catch (NoSuchMethodException ex) {
Debug.log(ex);
}
catch (InstantiationException ex) {
Debug.log(ex);
}
catch (IllegalAccessException ex) {
Debug.log(ex);
}
catch (IllegalArgumentException ex) {
Debug.log(ex);
}
catch (InvocationTargetException ex) {
Debug.log(ex);
}
if (value != null) {
option.setValue(value);
}
}
}
}
}
This way, I can save and retrieve values from files easily. The problem is that I don't want to repeat this code everywhere. Like as we know, enums can't be extended; so wherever I use this, I have to put all these methods there. I want only to declare the values and that if they should be persisted. No method definitions each time; any ideas?
Using an enum to hold configurable values like this looks like an entirely wrong design. Enums are singletons, so effectively you can only have one configuration active at any given time.
An EnumMap sounds more like what you need. It's external to the enum, so you can instantiate as many configurations as you need.
import java.util.*;
public class EnumMapExample {
static enum Options {
DEBUG, ALWAYS_SAVE, THREAD_COUNT;
}
public static void main(String[] args) {
Map<Options,Object> normalConfig = new EnumMap<Options,Object>(Options.class);
normalConfig.put(Options.DEBUG, false);
normalConfig.put(Options.THREAD_COUNT, 3);
System.out.println(normalConfig);
// prints "{DEBUG=false, THREAD_COUNT=3}"
Map<Options,Object> debugConfig = new EnumMap<Options,Object>(Options.class);
debugConfig.put(Options.DEBUG, true);
debugConfig.put(Options.THREAD_COUNT, 666);
System.out.println(debugConfig);
// prints "{DEBUG=true, THREAD_COUNT=666}"
}
}
API links
java.util.EnumMap
A specialized Map implementation for use with enum type keys. All of the keys in an enum map must come from a single enum type that is specified, explicitly or implicitly, when the map is created. Enum maps are represented internally as arrays. This representation is extremely compact and efficient.
i tried doing something similar with enum maps and properties files (please see code below). but my enums were simple and only had one value except for an embedded case. i may have something that is more type safe. i will look around for it.
package p;
import java.util.*;
import java.io.*;
public class GenericAttributes<T extends Enum<T>> {
public GenericAttributes(final Class<T> keyType) {
map = new EnumMap<T, Object>(this.keyType = keyType);
}
public GenericAttributes(final Class<T> keyType, final Properties properties) {
this(keyType);
addStringProperties(properties);
}
public Object get(final T key) {
// what does a null value mean?
// depends on P's semantics
return map.containsKey(key) ? map.get(key) : null;
}
public boolean contains(final T key) {
return map.containsKey(key);
}
public void change(final T key, final Object value) {
remove(key);
put(key, value);
}
public Object put(final T key, final Object value) {
if (map.containsKey(key))
throw new RuntimeException("map already contains: " + key);
else
return map.put(key, value);
}
public Object remove(final T key) {
if (!map.containsKey(key))
throw new RuntimeException("map does not contain: " + key);
return map.remove(key);
}
public String toString() {
return toString(defaultEquals, defaultEndOfLine);
}
// maybe we don;t need this stuff
// we have tests for it though
// it might be useful
public String toString(final String equals, final String endOfLine) {
final StringBuffer sb = new StringBuffer();
for (Map.Entry<T, Object> entry : map.entrySet())
sb.append(entry.getKey()).append(equals).append(entry.getValue()).append(endOfLine);
return sb.toString();
}
public Properties toProperties() {
final Properties p = new Properties();
for (Map.Entry<T, Object> entry : map.entrySet())
p.put(entry.getKey().toString(), entry.getValue().toString());
return p;
}
public void addStringProperties(final Properties properties) {
// keep this for strings, but mostly do work in the enum class
// i.e. static GenericAttributes<PA> fromProperties();
// which would use a fromString()
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
final String key = (String) entry.getKey();
final String value = (String) entry.getValue();
addProperty(key, value);
}
}
public void addProperty(final String key, final Object value) {
try {
final T e = Enum.valueOf(keyType, key);
map.put(e, value);
} catch (IllegalArgumentException e) {
System.err.println(key + " is not an enum from: " + keyType);
}
}
public int size() {
return map.size();
}
public static Properties load(final InputStream inputStream,final Properties defaultProperties) {
final Properties p=defaultProperties!=null?new Properties(defaultProperties):new Properties();
try {
p.load(inputStream);
} catch(IOException e) {
throw new RuntimeException(e);
}
return p;
}
public static Properties load(final File file,final Properties defaultProperties) {
Properties p=null;
try {
final InputStream is=new FileInputStream(file);
p=load(is,defaultProperties);
is.close();
} catch(IOException e) {
throw new RuntimeException(e);
}
return p;
}
public static void store(final OutputStream outputStream, final Properties properties) {
try {
properties.store(outputStream, null);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void store(final File file, final Properties properties) {
try {
final OutputStream os = new FileOutputStream(file);
store(os, properties);
os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
final Class<T> keyType;
static final String defaultEquals = "=", defaultEndOfLine = "\n";
private final EnumMap<T, Object> map;
public static void main(String[] args) {
}
}
package p;
import static org.junit.Assert.*;
import org.junit.*;
import java.io.*;
import java.util.*;
enum A1 {
foo,bar,baz;
}
enum A2 {
x,y,z;
}
public class GenericAttributesTestCase {
#Test public void testGenericAttributes() {
new GenericAttributes<A1>(A1.class);
}
#Test public void testGenericAttributesKeyTypeProperties() {
final Properties expected=gA1.toProperties();
final GenericAttributes<A1> gA=new GenericAttributes<A1>(A1.class,expected);
final Properties actual=gA.toProperties();
assertEquals(expected,actual);
}
#Test public void testGet() {
final A1 key=A1.foo;
emptyGA1.put(key,null);
final Object actual=emptyGA1.get(key);
assertEquals(null,actual);
}
#Test public void testGetInteger() {
// attributes.add(key,integer);
// assertEquals(integer,attributes.get("key"));
}
#Test public void testContains() {
for(A1 a:A1.values())
assertFalse(emptyGA1.contains(a));
}
#Test public void testChange() {
final A1 key=A1.foo;
final Integer value=42;
emptyGA1.put(key,value);
final Integer expected=43;
emptyGA1.change(key,expected);
final Object actual=emptyGA1.get(key);
assertEquals(expected,actual);
}
#Test public void testAdd() {
final A1 key=A1.foo;
final Integer expected=42;
emptyGA1.put(key,expected);
final Object actual=emptyGA1.get(key);
assertEquals(expected,actual);
}
#Test public void testRemove() {
final A1 key=A1.foo;
final Integer value=42;
emptyGA1.put(key,value);
emptyGA1.remove(key);
assertFalse(emptyGA1.contains(key));
}
#Test public void testToString() {
final String actual=gA1.toString();
final String expected="foo=a foo value\nbar=a bar value\n";
assertEquals(expected,actual);
}
#Test public void testToStringEqualsEndOfLine() {
final String equals=",";
final String endOFLine=";";
final String actual=gA1.toString(equals,endOFLine);
final String expected="foo,a foo value;bar,a bar value;";
assertEquals(expected,actual);
}
#Test public void testEmbedded() {
final String equals=",";
final String endOfLine=";";
//System.out.println("toString(\""+equals+"\",\""+endOFLine+"\"):");
final String embedded=gA1.toString(equals,endOfLine);
GenericAttributes<A2> gA2=new GenericAttributes<A2>(A2.class);
gA2.put(A2.x,embedded);
//System.out.println("embedded:\n"+gA2);
// maybe do file={name=a.jpg;dx=1;zoom=.5}??
// no good, key must be used more than once
// so file:a.jpg={} and hack
// maybe file={name=...} will work
// since we have to treat it specially anyway?
// maybe this is better done in ss first
// to see how it grows?
}
#Test public void testFromString() {
// final Attributes a=Attributes.fromString("");
// final String expected="";
// assertEquals(expected,a.toString());
}
#Test public void testToProperties() {
final Properties expected=new Properties();
expected.setProperty("foo","a foo value");
expected.setProperty("bar","a bar value");
final Properties actual=gA1.toProperties();
assertEquals(expected,actual);
}
#Test public void testAddProperties() {
final Properties p=gA1.toProperties();
final GenericAttributes<A1> ga=new GenericAttributes<A1>(A1.class);
ga.addStringProperties(p);
// assertEquals(ga1,ga); // fails since we need to define equals!
// hack, go backwards
final Properties p2=ga.toProperties();
assertEquals(p,p2); // hack until we define equals
}
#Test public void testStore() throws Exception {
final Properties expected=gA1.toProperties();
final ByteArrayOutputStream baos=new ByteArrayOutputStream();
GenericAttributes.store(baos,expected);
baos.close();
final byte[] bytes=baos.toByteArray();
final ByteArrayInputStream bais=new ByteArrayInputStream(bytes);
final Properties actual=GenericAttributes.load(bais,null);
bais.close();
assertEquals(expected,actual);
}
#Test public void testLoad() throws Exception {
final Properties expected=gA1.toProperties();
final ByteArrayOutputStream baos=new ByteArrayOutputStream();
GenericAttributes.store(baos,expected);
baos.close();
final ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
final Properties actual=GenericAttributes.load(bais,null);
bais.close();
assertEquals(expected,actual);
}
#Test public void testMain() {
// fail("Not yet implemented");
}
GenericAttributes<A1> gA1=new GenericAttributes<A1>(A1.class);
{
gA1.put(A1.foo,"a foo value");
gA1.put(A1.bar,"a bar value");
}
GenericAttributes<A1> emptyGA1=new GenericAttributes<A1>(A1.class);
}
answering your comment:
seems like i am getting values by using the enum as the key. i am probably confused.
an enum can implement an interface and each set of enums could have an instance of that base class and delegate calls to it (see item 34 of http://java.sun.com/docs/books/effective/toc.html)
i found the other code that went with my generic attributes (please see below), but i can't find any tests for it and am not quite sure what i was doing other than perhaps to add some stronger typing.
my motivation for all of this was to store some attributes for a photo viewer like picasa, i wanted to store a bunch of attributes for a picture in a single line of a property file
package p;
import java.util.*;
public enum GA {
// like properties, seems like this wants to be constructed with a set of default values
i(Integer.class) {
Integer fromString(final String s) {
return new Integer(s);
}
Integer fromNull() {
return zero; // return empty string?
}
},
b(Boolean.class) {
Boolean fromString(final String s) {
return s.startsWith("t")?true:false;
}
Boolean fromNull() {
return false;
}
},
d(Double.class) {
Double fromString(final String s) {
return new Double(s);
}
Double fromNull() {
return new Double(zero);
}
};
GA() {
this(String.class);
}
GA(final Class clazz) {
this.clazz=clazz;
}
abstract Object fromString(String string);
abstract Object fromNull();
static GenericAttributes<GA> fromProperties(final Properties properties) {
final GenericAttributes<GA> pas=new GenericAttributes<GA>(GA.class);
for(Map.Entry<Object,Object> entry:properties.entrySet()) {
final String key=(String)entry.getKey();
final GA pa=valueOf(key);
if(pa!=null) {
final String stringValue=(String)entry.getValue();
Object value=pa.fromString(stringValue);
pas.addProperty(key,value);
} else throw new RuntimeException(key+"is not a member of "+"GA");
}
return pas;
}
// private final Object defaultValue; // lose type?; require cast?
/* private */final Class clazz;
static final Integer zero=new Integer(0);
}
If you are still looking for answers, you could give a try to Properties library which is open-source with MIT license. Using this, you won't have to specify string constants and everything will be determined by an enum defined by you. And, it has some other features too. Highlights of this library are:
All property keys are defined in a single place, i.e. a user defined enum
Property values can contain variables (starting with $ sign, e.g. $PATH) where PATH is a property key in same file
Property value can be obtained as specified data type, so no need to convert string value to required data type
Property value can be obtained as list of specified data types
Property value can be a multi-line text
Can make property keys mandatory or optional
Can specify default value for the property key if value is not available
Is thread safe
You can find sample programs here
Related
I was given a class (which I could not change) with a lot of fields of String, each of them have a set method.
public class C {
private String fieldA,
private String fieldB,
...
public void setFieldA(String s) {
this.fieldA = s;
}
public void setFieldB(String s) {
this.fieldB = s;
}
...
}
I need to write code to create an instance of the class and initialize it with a json object (which will have value for some or all of the fields).
The json is very simple and looks like:
{"fieldA":"valueA", "fieldB":"valueB"..... }
I know I could write something like below for every field:
if (myJsonObj.containsKey("fieldA"){
myInstance.setFieldA(myJsonObj.get("fieldA").toString());
}
I'm wondering if there is a smarter way I could do this, since the number of fields could be up to about thirty. Instead of repeating the above code thirty times (and the only thing different between each is "fieldA" in the above code).
Thanks!
Sounds like a job for reflection to me. Everything except the bit marked with "Reflection" is here solely to make this an SSCCE. You'll likely have your own way to track which methods are available or whatever. Take a look at the following site for an (IMHO) excellent guide (I am not affiliated with the site in any way): Guide to Java Reflection
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
private String fieldA;
private String fieldB;
private String fieldC;
private String fieldD;
private String fieldE;
public String getFieldA() {
return fieldA;
}
public void setFieldA(final String fieldA) {
this.fieldA = fieldA;
}
public String getFieldB() {
return fieldB;
}
public void setFieldB(final String fieldB) {
this.fieldB = fieldB;
}
public String getFieldC() {
return fieldC;
}
public void setFieldC(final String fieldC) {
this.fieldC = fieldC;
}
public String getFieldD() {
return fieldD;
}
public void setFieldD(final String fieldD) {
this.fieldD = fieldD;
}
public String getFieldE() {
return fieldE;
}
public void setFieldE(final String fieldE) {
this.fieldE = fieldE;
}
private static final char[] chars = {'A', 'B', 'C', 'D', 'E'};
private static final String[] values =
{"valueA", "valueB", "valueC", "valueD", "valueE"};
private static final Class<?>[] types = {String.class};
public static void main(final String[] args)
throws SecurityException, NoSuchMethodException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException {
// Reflection setting values - everything else is here solely to make this an SSCCE
final Test test = new Test();
final Class<Test> clazz = Test.class;
for(int i = 0; i < chars.length; i++) {
final String name = "setField" + chars[i];
final Method method = clazz.getDeclaredMethod(name, types);
final Object[] params = {values[i]};
method.invoke(test, params);
}
// End of reflection
System.out.println(test.getFieldA());
System.out.println(test.getFieldB());
System.out.println(test.getFieldC());
System.out.println(test.getFieldD());
System.out.println(test.getFieldE());
}
}
Thanks to Chris Parker.
Here is my final code based on his original code. Because there are many String fields in the class and it might change in the future, so using reflect makes the update/change very easy: I only need to change the String Array.
The given class is like below (only String fields and their set methods are shown):
public class MyClass {
private String fieldA,
private String fieldB,
...
public void setFieldA(String s) {
this.fieldA = s;
}
public void setFieldB(String s) {
this.fieldB = s;
}
...
}
The method to set the value of those string fields using a JSONObject input to an instance of C:
public void fillClassStringFields(MyClass c, JSONObject input) {
String[] names = {"FieldA","FieldB",...};
Class<MyClass> clazz = MyClass.class;
Class<?>[] types = {String.class};
for(int i = 0; i< names.length;i++) {
String name = names[i].toString();
String methodName = "set" + name;
if(input.containsKey(name)) {
try {
Method method = clazz.getDeclaredMethod(methodName, types);
method.invoke(c, input.get(name).toString());
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
I have a working annotation processor that gathers information of the annotated classes. Everything is there during compilation. But I would like to have access to those results during runtime.
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
private final static List<TestInfo> tests = new ArrayList<>();
#Override
public Set getSupportedAnnotationTypes() {
return new LinkedHashSet() {
{
add(Annotation.class.getCanonicalName());
}
};
}
#Override
public boolean process(final Set<? extends TypeElement> annotations,
final RoundEnvironment env) {
System.out.println("Processing!");
if (!env.processingOver()) {
Set<? extends Element> rootE = env.getRootElements();
for (Element e : rootE) {
if (e.getKind() == ElementKind.CLASS) {
TestInfo t = new TestInfo(e.asType().toString());
for (Element se : e.getEnclosedElements()) {
if (se.getKind() == ElementKind.METHOD) {
t.addMethod(se.getSimpleName().toString());
}
}
getTests().add(t);
}
}
getTests().forEach(ti -> {
System.out.println(ti);
});
}
return false;
}
public static TypeElement findEnclosingTypeElement(Element e) {
while (e != null && !(e instanceof TypeElement)) {
e = e.getEnclosingElement();
}
return TypeElement.class.cast(e);
}
/**
* #return the tests
*/
public static List<TestInfo> getTests() {
return tests;
}
}
Is there a way to retrieve the results at runtime? TestProcessor.getTests returns an empty list.
Here's the TestInfo class fyi:
public class TestInfo {
private final String name;
private final List<String> methods = new ArrayList<>();
public TestInfo(String name) {
this.name = name;
}
public void addMethod(String m) {
getMethods().add(m);
}
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #return the methods
*/
public List<String> getMethods() {
return methods;
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(name).append(methods.toString());
return sb.toString();
}
}
Update: The annotation is marked with retention runtime.
The annotation-processing is in compile time. So you can't get the information in the runtime.
A direct way is to write the information as a resource file in compile time and read it at runtime.
Here is my example:
The annotation:
#Retention(SOURCE)
#Target(TYPE)
public #interface Anno {
}
The processor:
#Override
public boolean processActual(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return false;
}
try {
write(roundEnv);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
private void write(RoundEnvironment roundEnv) throws IOException, UnsupportedEncodingException {
Filer filer = processingEnv.getFiler();
FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", "TestInfo");
OutputStream output = resource.openOutputStream();
PrintStream writer = new PrintStream(output, false, "UTF-8");
roundEnv.getElementsAnnotatedWith(Anno.class)
.stream()
.filter(e -> e.getKind() == ElementKind.CLASS)
.map(e -> e.asType().toString())
.forEach(writer::println);
writer.flush();
}
And the user code:
#Anno
public class Q48177784 {
public static final List<Class<?>> CLASSES;
static {
try {
URL resource = Q48177784.class.getClassLoader().getResource("TestInfo");
CLASSES = Files.readAllLines(Paths.get(resource.toURI()))
.stream()
.map(s -> {
try {
return Class.forName(s);
} catch (ClassNotFoundException e) {
throw new Error(e);
}
})
.collect(Collectors.toList());
} catch (Exception e) {
throw new Error(e);
}
}
public static void main(String[] args) {
System.out.println(CLASSES);
}
}
After build with processor, run the main method:
[class xdean.stackoverflow.Q48177784]
For your case, the only thing you should do is serialize/deserialize your TestInfo
Check out #RetentionPolicy. I think you want to set it to RUNTIME.
I am trying to test this method in a class called AbcDAO:
public List<AbcVO> getCost(List<AbcVO> items) throws ParseException {
try {
String var;
final Map<Object, Object> params = new HashMap<Object, Object>();
for (final AbcVO abcVO : items) {
if (Xyz.isTime(abcVO.getDate())
{
if (AppConstant.IT1.equalsIgnoreCase(abcVO.getItemFlag())
|| AppConstant.IT2.equalsIgnoreCase(abcVO.getItemFlag())) {
var = "IT_New";
} else {
var = "IT_Old";
}
final List<Map<String, Object>> result = this.runQuery(var);
{Code to populate abcVO's cost field with amt}
}
}
return items;
} catch (final ParseException e) {
throw new Exception("AbcDAO", "getCost", e, AppConstant.ERR_STRING);
}
}
As we see there is a static call to abcVO.getDate and its in the XyzDAO class and looks like:
public static boolean getDate(Timestamp ts) throws ParseException {
final java.util.Date date = new SimpleDateFormat("MM/dd/yyyy").parse(AppConfig.ProcDate);
final Timestamp ts = new Timestamp(date.getTime());
if (ts.compareTo(ts) < 0)
{
return true;
} else {
return false;
}
}
What I want to test is simple - I want to verify that if I set the itemFlag to AppConstant.IT2, the var should equal IT_new and verify the return amount.
Here is my test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:test-context.xml" })
public class AbcDAOTest {
#Autowired
private AbcDAO abcDAO;
#Before
public void setUp() throws Exception {
}
#Test
public final void testGetCost() throws MIBException {
// Create the item list
final AbcVO abcVO = new AbcVO();
abcVO.setItemFlag(AppConstant.IT2);
final List<AbcVO> items = new ArrayList<AbcVO>();
items.add(abcVO);
this.abcDAO.getCost(items);
final String expectedCost = "1237";
assertEquals(items.get(0).getCost(), expectedCost));
}
}
The problem I'm running into is I'm getting a NPE on .parse(AppConfig.ProcDate) on the static call as the ProcDate is null.
My question is can I inject that value into this test (e.g. AppConfig.ProcDate = "12/01/16") ? Also how can I verify the value of var which is a private variable?
Else, is there some other way to test this?
I've seen that the default TypeAdapter for Enum doesn't fit my need:
private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
private final Map<String, T> nameToConstant = new HashMap<String, T>();
private final Map<T, String> constantToName = new HashMap<T, String>();
public EnumTypeAdapter(Class<T> classOfT) {
try {
for (T constant : classOfT.getEnumConstants()) {
String name = constant.name();
SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
if (annotation != null) {
name = annotation.value();
}
nameToConstant.put(name, constant);
constantToName.put(constant, name);
}
} catch (NoSuchFieldException e) {
throw new AssertionError();
}
}
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return nameToConstant.get(in.nextString());
}
public void write(JsonWriter out, T value) throws IOException {
out.value(value == null ? null : constantToName.get(value));
}
}
If the Enum has value ONE and TWO, when we try to parse THREE, then this value is unknown and Gson will map null instead of raising a parsing exception. I need something more fail-fast.
But I also need something which permits me to know the name of the field which is currently read and creates a parsing failure.
Is it possible with Gson?
Yes.
Gson is quite modular to allow you to use your own TypeAdapterFactory for the enum case. Your custom adapter will return your own EnumTypeAdapter and manage the wanted case. Let the code speak.
package stackoverflow.questions.q16715117;
import java.io.IOException;
import java.util.*;
import com.google.gson.*;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.*;
public class Q16715117 {
public static void main(String[] args) {
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapterFactory(CUSTOM_ENUM_FACTORY);
Container c1 = new Container();
Gson g = gb.create();
String s1 = "{\"colour\":\"RED\",\"number\":42}";
c1 = g.fromJson(s1, Container.class);
System.out.println("Result: "+ c1.toString());
}
public static final TypeAdapterFactory CUSTOM_ENUM_FACTORY = newEnumTypeHierarchyFactory();
public static TypeAdapterFactory newEnumTypeHierarchyFactory() {
return new TypeAdapterFactory() {
#SuppressWarnings({"rawtypes", "unchecked"})
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) {
return null;
}
if (!rawType.isEnum()) {
rawType = rawType.getSuperclass(); // handle anonymous subclasses
}
return (TypeAdapter<T>) new CustomEnumTypeAdapter(rawType);
}
};
}
private static final class CustomEnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
private final Map<String, T> nameToConstant = new HashMap<String, T>();
private final Map<T, String> constantToName = new HashMap<T, String>();
private Class<T> classOfT;
public CustomEnumTypeAdapter(Class<T> classOfT) {
this.classOfT = classOfT;
try {
for (T constant : classOfT.getEnumConstants()) {
String name = constant.name();
SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
if (annotation != null) {
name = annotation.value();
}
nameToConstant.put(name, constant);
constantToName.put(constant, name);
}
} catch (NoSuchFieldException e) {
throw new AssertionError();
}
}
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
String nextString = in.nextString();
T enumValue = nameToConstant.get(nextString);
if (enumValue == null)
throw new GsonEnumParsinException(nextString, classOfT.getName());
return enumValue;
}
public void write(JsonWriter out, T value) throws IOException {
out.value(value == null ? null : constantToName.get(value));
}
}
}
Plus I declared a custom runtime exception:
public class GsonEnumParsinException extends RuntimeException {
String notFoundEnumValue;
String enumName;
String fieldName;
public GsonEnumParsinException(String notFoundEnumValue, String enumName) {
this.notFoundEnumValue = notFoundEnumValue;
this.enumName = enumName;
}
#Override
public String toString() {
return "GsonEnumParsinException [notFoundEnumValue="
+ notFoundEnumValue + ", enumName=" + enumName + "]";
}
public String getNotFoundEnumValue() {
return notFoundEnumValue;
}
#Override
public String getMessage() {
return "Cannot found " + notFoundEnumValue + " for enum " + enumName;
}
}
These are the classes I used in the example:
public enum Colour {
WHITE, YELLOW, BLACK;
}
public class Container {
private Colour colour;
private int number;
public Colour getColour() {
return colour;
}
public void setColour(Colour colour) {
this.colour = colour;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
#Override
public String toString() {
return "Container [colour=" + colour + ", number=" + number + "]";
}
}
This gives this stacktrace:
Exception in thread "main" GsonEnumParsinException [notFoundEnumValue=RED, enumName=stackoverflow.questions.q16715117.Colour]
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:77)
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:1)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172)
at com.google.gson.Gson.fromJson(Gson.java:803)
at com.google.gson.Gson.fromJson(Gson.java:768)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at stackoverflow.questions.q16715117.Q16715117.main(Q16715117.java:22)
Unfortunately, the EnumTypeAdapter does not know anything about the context it's called, so this solution is not enough to catch the field name.
Edit
So you have to use also another TypeAdapter that I called CustomReflectiveTypeAdapterFactory and is almost a copy of CustomReflectiveTypeAdapterFactory and I changed a bit the exception, so:
public final class CustomReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor;
private final FieldNamingStrategy fieldNamingPolicy;
private final Excluder excluder;
public CustomReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
this.constructorConstructor = constructorConstructor;
this.fieldNamingPolicy = fieldNamingPolicy;
this.excluder = excluder;
}
public boolean excludeField(Field f, boolean serialize) {
return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
}
private String getFieldName(Field f) {
SerializedName serializedName = f.getAnnotation(SerializedName.class);
return serializedName == null ? fieldNamingPolicy.translateName(f) : serializedName.value();
}
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
}
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}
private CustomReflectiveTypeAdapterFactory.BoundField createBoundField(
final Gson context, final Field field, final String name,
final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
// special casing primitives here saves ~5% on Android...
return new CustomReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
final TypeAdapter<?> typeAdapter = context.getAdapter(fieldType);
#SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
#Override void write(JsonWriter writer, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = field.get(value);
TypeAdapter t =
new CustomTypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType());
t.write(writer, fieldValue);
}
#Override void read(JsonReader reader, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = null;
try {
fieldValue = typeAdapter.read(reader);
} catch (GsonEnumParsinException e){
e.setFieldName(field.getName());
throw e;
}
if (fieldValue != null || !isPrimitive) {
field.set(value, fieldValue);
}
}
};
}
// more copy&paste code follows
The most important part is read method where I catch the exception and add the field name and throw again exception. Note that class CustomTypeAdapterRuntimeTypeWrapper is simply a renamed copy of TypeAdapterRuntimeTypeWrapper in library internals since class is private.
So, main method changes as follows:
Map<Type, InstanceCreator<?>> instanceCreators
= new HashMap<Type, InstanceCreator<?>>();
Excluder excluder = Excluder.DEFAULT;
FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapterFactory(new CustomReflectiveTypeAdapterFactory(new ConstructorConstructor(instanceCreators), fieldNamingPolicy, excluder));
gb.registerTypeAdapterFactory(CUSTOM_ENUM_FACTORY);
Gson g = gb.create();
and now you have this stacktrace (changes to exception are so simple that I omitted them):
Exception in thread "main" GsonEnumParsinException [notFoundEnumValue=RED, enumName=stackoverflow.questions.q16715117.Colour, fieldName=colour]
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:90)
at stackoverflow.questions.q16715117.Q16715117$CustomEnumTypeAdapter.read(Q16715117.java:1)
at stackoverflow.questions.q16715117.CustomReflectiveTypeAdapterFactory$1.read(CustomReflectiveTypeAdapterFactory.java:79)
at stackoverflow.questions.q16715117.CustomReflectiveTypeAdapterFactory$Adapter.read(CustomReflectiveTypeAdapterFactory.java:162)
at com.google.gson.Gson.fromJson(Gson.java:803)
at com.google.gson.Gson.fromJson(Gson.java:768)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at stackoverflow.questions.q16715117.Q16715117.main(Q16715117.java:35)
Of course this solution comes at some costs.
First off all, you have to copy some private/final classes and do your changes. If library get updated, you have to check again your code (a fork of source code would be the same, but at least you do not have to copy all that code).
If you customize field exclusion strategy, constructors or field naming policies you have to replicate them into the CustomReflectiveTypeAdapterFactory since I do not find any possibility to pass them from the builder.
I have the following class:
class MyClass{
private static final int VERSION_VALUE = 8;
private static final String VERSION_KEY = "versionName";
public boolean myPublicMethod(String str) {
try {
return myPrivateMethod(str, VERSION_KEY, VERSION_VALUE,
new MyInnerClass() {
#Override
public InputStream loadResource(String name) {
//do something important
}
});
}
catch (Exception e) {
}
return false;
}
private boolean myPrivateMethod(String str, String key, int version,
ResourceLoader resourceLoader) throws Exception
{
//do something
}
private static abstract class MyInnerClass {
public abstract InputStream loadResource(String name);
}
}
I want to write unit test for myPrivateMethod for which I need to pass resourceLoader object and override it's loadResource method.
Here is my test method:
#Test
public void testMyPrivateMethod() throws Exception {
Class<?> cls = Class.forName("my.pack.MyClass$MyInnerClass");
Method method = cls.getDeclaredMethod("loadResource", String.class);
//create inner class instance and override method
Whitebox.invokeMethod(myClassObject, "testValue1", "testValue2", "name1", 10, innerClassObject);
}
Note, that I can't change code.
Well, you could use Javassist...
See this question. I haven't tried this, but you can call this method when you want the override:
public <T extends Object> T getOverride(Class<T> cls, MethodHandler handler) {
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(cls);
factory.setFilter(
new MethodFilter() {
#Override
public boolean isHandled(Method method) {
return Modifier.isAbstract(method.getModifiers());
}
}
);
return (T) factory.create(new Class<?>[0], new Object[0], handler);
}
Well, the problem i see with your code is that you are calling myPublicMethod and you are giving fourth parameter as new MyInnerClass(). Now in your private method fourth parameter is given as ResourceLoader and from your code i see no relation between MyInnerClass and ResourceLoader. So you can try out following code. It might help.
Despite your warning that you cannot change the code i have changed it because i was trying to run your code.
class MyClass{
private static final int VERSION_VALUE = 8;
private static final String VERSION_KEY = "versionName";
public boolean myPublicMethod(String str) {
try {
return myPrivateMethod(str, VERSION_KEY, VERSION_VALUE,
new MyInnerClass() {
#Override
public InputStream loadResource(String name) {
return null;
//do something important
}
});
}
catch (Exception e) {
}
return false;
}
private boolean myPrivateMethod(String str, String key, int version,
MyInnerClass resourceLoader) throws Exception
{
return false;
//do something
}
private static abstract class MyInnerClass {
public abstract InputStream loadResource(String name);
}
}
Hope it helps.