Store and load POJO to/from All-Users repository using reflection

The following types are currently supported:

* String
* Integer|int
* Long|long
* Boolean|boolean
* Enum

Every POJO has to provide default instance to optimize data storage.
Fields that contain default value are skipped during save operation
and restored during read operation; so that this optimization step
is transparent for the callers.

Fields marked with final or transient modifiers are skipped during
serialization.

Change-Id: I11f31d6564e567b4aa6f50009fa7684917b4046a
This commit is contained in:
David Ostrovsky
2015-05-13 00:05:07 +02:00
parent 6a3e1514d3
commit 85279183ec
2 changed files with 221 additions and 0 deletions

View File

@@ -14,11 +14,17 @@
package com.google.gerrit.server.config;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -251,6 +257,133 @@ public class ConfigUtil {
return v;
}
/**
* Store section by inspecting Java class attributes.
* <p>
* Optimize the storage by unsetting a variable if it is
* being set to default value by the server.
* <p>
* Fields marked with final or transient modifiers are skipped.
*
* @param cfg config in which the values should be stored
* @param section section
* @param sub subsection
* @param s instance of class with config values
* @params defaults instance of class with default values
* @throws ConfigInvalidException
*/
public static <T> void storeSection(Config cfg, String section, String sub,
T s, T defaults) throws ConfigInvalidException {
try {
for (Field f : s.getClass().getDeclaredFields()) {
if (skipField(f)) {
continue;
}
Class<?> t = f.getType();
String n = f.getName();
f.setAccessible(true);
Object c = f.get(s);
Object d = f.get(defaults);
Preconditions.checkNotNull(d, "Default cannot be null");
if (c == null || c.equals(d)) {
cfg.unset(section, sub, n);
} else {
if (isString(t)) {
cfg.setString(section, sub, n, (String) c);
} else if (isInteger(t)) {
cfg.setInt(section, sub, n, (Integer) c);
} else if (isLong(t)) {
cfg.setLong(section, sub, n, (Long) c);
} else if (isBoolean(t)) {
cfg.setBoolean(section, sub, n, (Boolean) c);
} else if (t.isEnum()) {
cfg.setEnum(section, sub, n, (Enum<?>) c);
} else {
throw new ConfigInvalidException("type is unknown: " + t.getName());
}
}
}
} catch (SecurityException | IllegalArgumentException
| IllegalAccessException e) {
throw new ConfigInvalidException("cannot save values", e);
}
}
/**
* Load section by inspecting Java class attributes.
* <p>
* Config values are stored optimized: no default values are stored.
* The loading is performed eagerly: all values are set.
* <p>
* Fields marked with final or transient modifiers are skipped.
* <p>
* Boolean fields are only set when their values are true.
*
* @param cfg config from which the values are loaded
* @param section section
* @param sub subsection
* @param s instance of class in which the values are set
* @params defaults instance of class with default values
* @return loaded instance
* @throws ConfigInvalidException
*/
public static <T> T loadSection(Config cfg, String section, String sub,
T s, T defaults) throws ConfigInvalidException {
try {
for (Field f : s.getClass().getDeclaredFields()) {
if (skipField(f)) {
continue;
}
Class<?> t = f.getType();
String n = f.getName();
f.setAccessible(true);
Object d = f.get(defaults);
Preconditions.checkNotNull(d, "Default cannot be null");
if (isString(t)) {
f.set(s, MoreObjects.firstNonNull(cfg.getString(section, sub, n), d));
} else if (isInteger(t)) {
f.set(s, cfg.getInt(section, sub, n, (Integer) d));
} else if (isLong(t)) {
f.set(s, cfg.getLong(section, sub, n, (Long) d));
} else if (isBoolean(t)) {
boolean b = cfg.getBoolean(section, sub, n, (Boolean) d);
if (b) {
f.set(s, b);
}
} else if (t.isEnum()) {
f.set(s, cfg.getEnum(section, sub, n, (Enum<?>) d));
} else {
throw new ConfigInvalidException("type is unknown: " + t.getName());
}
}
} catch (SecurityException | IllegalArgumentException
| IllegalAccessException e) {
throw new ConfigInvalidException("cannot load values", e);
}
return s;
}
private static boolean skipField(Field field) {
int modifiers = field.getModifiers();
return Modifier.isFinal(modifiers) || Modifier.isTransient(modifiers);
}
private static boolean isString(Class<?> t) {
return String.class == t;
}
private static boolean isBoolean(Class<?> t) {
return Boolean.class == t || boolean.class == t;
}
private static boolean isLong(Class<?> t) {
return Long.class == t || long.class == t;
}
private static boolean isInteger(Class<?> t) {
return Integer.class == t || int.class == t;
}
private static boolean match(final String a, final String... cases) {
for (final String b : cases) {
if (equalsIgnoreCase(a, b)) {