Find direct and indirect subclasses by scanning filesystem - java

I'm having a problem in writing an algorithm to help me scan a file system and find all subclasses of a certain class.
Details:
I've an app that scans an external application using nio Files.walk() while retrieving I check for "extends SuperClass" while reading the file if the word exits, I add the class name in my list as follows:
List<String> subclasses = new ArrayList<>();
Files.walk(appPath)
.filter(p->Files.isRegularFile(p) && p.toString()
.endsWith(".java")).forEach(path -> {
try {
List<String> lines = Files.readAllLines(path);
Pattern pattern = Pattern.compile("\\bextends SuperClass\\b");
Matcher matcher = pattern
.matcher(lines.stream()
.collect(Collectors.joining(" ")));
boolean isChild = matcher.find();
if(isChild) subclasses.add(path.getFileName().toString());
}catch (IOException e){
//handle IOE
}
The problem with the above is that it only gets direct subclasses of SuperClass but I need to retrieve all direct and indirect subclasses.
I thought about recursion since I've no Idea how many subclasses of SuperClass there is but I couldn't implement any reasonable implementation.
NOTES:
Scanning more than 600 thousands file
I have no Idea how many direct/indirect subclasses of SuperClass there is
The application that I'm scanning is external and I can't modify its code so I'm only allowed to access it by reading files and see where extends exists
If there is a non-recursive solution to the problem that would be great but if there's no other way, I'll be more than happy to accept a recursive one since I care about the solution more than performance.
Edit:
I use the following regex to compare both name and import to make sure even in case of same name different packages the output is correct:
Pattern pattern = Pattern.compile("("+superClasss.getPackage()+")[\\s\\S]*(\\bextends "+superClass.getName()+"\\b)[\\s\\S]");
I also tried:
Pattern pattern = Pattern.compile("\\bextends "+superClass.getName()+"\\b");
But there is also some missing subclasses, I believe that the code bellow skips some checks, and doesn't fully work:
public static List<SuperClass> getAllSubClasses(Path path, SuperClass parentClass) throws IOException{
classesToDo.add(baseClass);
while(classesToDo.size() > 0) {
SuperClass superClass = classesToDo.remove(0);
List<SuperClass> subclasses = getDirectSubClasses(parentPath,parentClass);
if(subclasses.size() > 0)
classes.addAll(subclasses);
classesToDo.addAll(subclasses);
}
return classes;
}
Any help is truly appreciated!
Edit 2
I also noticed another problem, is that when I detect a subclass I get the file name currentPath.getFileName() which might or might not be the subclass name as the subclass may be a nested or non-public class in the same file.

I strongly recommend parsing compiled class files instead of source code. Since these class files are already optimized for being processed by machines, a lot of the complexity and corner cases of the source code file processing has been eliminated.
So a solution to build a complete class hierarchy tree using the ASM library would look like this:
public static Map<String, Set<String>> getClassHierarchy(Path root) throws IOException {
return Files.walk(root)
.filter(p->Files.isRegularFile(p) && isClass(p.getFileName().toString()))
.map(p -> getClassAndSuper(p))
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));
}
private static boolean isClass(String fName) {
// skip package-info and module-info
return fName.endsWith(".class") && !fName.endsWith("-info.class");
}
private static Map.Entry<String,String> getClassAndSuper(Path p) {
final class CV extends ClassVisitor {
Map.Entry<String,String> result;
public CV() {
super(Opcodes.ASM5);
}
#Override
public void visit(int version, int access,
String name, String signature, String superName, String[] interfaces) {
result = new AbstractMap.SimpleImmutableEntry<>(
Type.getObjectType(name).getClassName(),
superName!=null? Type.getObjectType(superName).getClassName(): "");
}
}
try {
final CV visitor = new CV();
new ClassReader(Files.readAllBytes(p)).accept(visitor, ClassReader.SKIP_CODE);
return visitor.result;
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
As a bonus, resp. to create some test cases, the following method adds the ability to build the hierarchy for a runtime class’ source:
public static Map<String, Set<String>> getClassHierarchy(Class<?> context)
throws IOException, URISyntaxException {
Path p;
URI clURI = context.getResource(context.getSimpleName()+".class").toURI();
if(clURI.getScheme().equals("jrt")) p = Paths.get(URI.create("jrt:/modules"));
else {
if(!clURI.getScheme().equals("file")) try {
FileSystems.getFileSystem(clURI);
} catch(FileSystemNotFoundException ex) {
FileSystems.newFileSystem(clURI, Collections.emptyMap());
}
String qn = context.getName();
p = Paths.get(clURI).getParent();
for(int ix = qn.indexOf('.'); ix>0; ix = qn.indexOf('.', ix+1)) p = p.getParent();
}
return getClassHierarchy(p);
}
Then, you can do
Map<String, Set<String>> hierarchy = getClassHierarchy(Number.class);
System.out.println("Direct subclasses of "+Number.class);
hierarchy.getOrDefault("java.lang.Number", Collections.emptySet())
.forEach(System.out::println);
and get
Direct subclasses of class java.lang.Number
java.lang.Float
java.math.BigDecimal
java.util.concurrent.atomic.AtomicLong
java.lang.Double
java.lang.Long
java.util.concurrent.atomic.AtomicInteger
java.lang.Short
java.math.BigInteger
java.lang.Byte
java.util.concurrent.atomic.Striped64
java.lang.Integer
or
Map<String, Set<String>> hierarchy = getClassHierarchy(Number.class);
System.out.println("All subclasses of "+Number.class);
printAllClasses(hierarchy, "java.lang.Number", " ");
private static void printAllClasses(
Map<String, Set<String>> hierarchy, String parent, String i) {
hierarchy.getOrDefault(parent, Collections.emptySet())
.forEach(x -> {
System.out.println(i+x);
printAllClasses(hierarchy, x, i+" ");
});
}
to get
All subclasses of class java.lang.Number
java.lang.Float
java.math.BigDecimal
java.util.concurrent.atomic.AtomicLong
java.lang.Double
java.lang.Long
java.util.concurrent.atomic.AtomicInteger
java.lang.Short
java.math.BigInteger
java.lang.Byte
java.util.concurrent.atomic.Striped64
java.util.concurrent.atomic.LongAdder
java.util.concurrent.atomic.LongAccumulator
java.util.concurrent.atomic.DoubleAdder
java.util.concurrent.atomic.DoubleAccumulator
java.lang.Integer

DISCLAIMER: This solution might not work if you have several classes with the same name as it does not take packages names into account.
I think you can do it with keeping track of the classes to lookup in a List and use a while loop until all the values on the list have been explored.
Here is a bit of code which creates a Map<String, List<String>>, key is the class name, value is the list of child classes.
public class Test {
private static Path appPath = //your path
private static Map<String, List<String>> classes = new HashMap<>();
private static List<String> classesToDo = new ArrayList<>();
public static void main(String[] args) throws IOException {
classesToDo.add("AnswerValueValidatorBase");
while(classesToDo.size() > 0) {
String className = classesToDo.remove(0);
List<String> subclasses = getDirectSubclasses(className);
if(subclasses.size() > 0)
classes.put(className, subclasses);
classesToDo.addAll(subclasses);
}
System.out.println(classes);
}
private static List<String> getDirectSubclasses(String className) throws IOException {
List<String> subclasses = new ArrayList<>();
Files.walk(appPath)
.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java"))
.forEach(path -> {
try {
List<String> lines = Files.readAllLines(path);
Pattern pattern = Pattern.compile("\\bextends "+className+"\\b");
Matcher matcher = pattern.matcher(lines.stream().collect(Collectors.joining(" ")));
boolean isChild = matcher.find();
if(isChild) {
String fileName = path.getFileName().toString();
String clazzName = fileName.substring(0, fileName.lastIndexOf("."));
subclasses.add(clazzName);
}
} catch(IOException e) {
//handle IOE
}
});
return subclasses;
}
}
Running it on my project returns something that looks correct
{
AnswerValueValidatorBase=[SingleNumericValidator, DefaultValidator, RatingValidator, ArrayValidatorBase, DocumentValidator],
ArrayValidatorBase=[MultiNumericValidator, StringArrayValidator, IntegerArrayValidator, MultiCheckboxValidator],
DefaultValidator=[IntegerValidator, DateValidator, StringValidator, CountryValidator, PercentageValidator],
IntegerArrayValidator=[MultiPercentageValidator, RankValidator, MultiDropValidator, MultiRadioValidator, CheckboxValidator],
SingleNumericValidator=[SliderValidator],
MultiNumericValidator=[MultiSliderValidator],
StringArrayValidator=[MultiTextValidator, ChecklistValidator]
}
EDIT
A recursive way of doing it would be
public class Test {
private static Path appPath = // your path
public static void main(String[] args) throws IOException {
List<String> classesToDo = new ArrayList<>();
classesToDo.add("AnswerValueValidatorBase");
Map<String, List<String>> classesMap = getSubclasses(new HashMap<>(), classesToDo);
System.out.println(classesMap);
}
private static Map<String, List<String>> getSubclasses(Map<String, List<String>> classesMap, List<String> classesToDo) throws IOException {
if(classesToDo.size() == 0) {
return classesMap;
} else {
String className = classesToDo.remove(0);
List<String> subclasses = getDirectSubclasses(className);
if(subclasses.size() > 0)
classesMap.put(className, subclasses);
classesToDo.addAll(subclasses);
return getSubclasses(classesMap, classesToDo);
}
}
private static List<String> getDirectSubclasses(String className) throws IOException {
// same as above
}
}

Related

should I use strategy pattern, If I have hundreds of actions

I have a class doing translate job. But it have hundreds of specific translate methods! The action code determine which method will be used! I want to use strategy pattern, but it will create hundreds of sub class! I want to name the methods end of action code and use reflection to do the translate, but I'm concern abort the execution performances. It will be called very frequently! What design pattern or patterns should I use to solve this problem!
code like this:
public class Test003_Translate {
private static final String PREFIX = "translate";
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Test003_Translate translate = new Test003_Translate();
Map<String, String> map = new HashMap<>();
map.put("key001", "001");
map.put("key002", "002");
map.put("key003", "003");
translate.doTranslate(map, "key001");
}
private void doTranslate(Map<String, String> map, String key) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String actionCode = map.get(key);
Method method = Test003_Translate.class.getMethod(PREFIX + actionCode, String.class);
String arg = "arg: ";
Object s = method.invoke(this, arg);
}
public String translate001(String input){
return input + "001";
}
public String translate002(String input){
return input + "002";
}
public String translate003(String input){
return input + "003";
}
}
You could use an EnumMap (smaller and faster then a HashMap), like this:
enum Key {
KEY_001,
....
}
EnumMap<Key, Runnable> enumMap = new EnumMap<>(Key.class);
enumMap.put(Key.KEY_001, YourClass::translate001);
....
And usage:
enumMap.get(someKey).run();

Calling setter from Top Level of Modelclass hierarchy

Imagine you have a model class hierarchy, like
public class TopLevel {
private MiddleLevel middleLevel = null;
public TopLevel() {
middleLevel = new MiddleLevel();
}
public MiddleLevel getMiddleLevel() { return middleLevel; }
}
public class MiddleLevel {
private LowLevel lowLevel = null;
public MiddleLevel () {
lowLevel = new LowLevel();
}
public LowLevel getLowLevel() { return lowLevel; }
}
public class LowLevel {
private Value value = null;
public LowLevel() {
value = new Value();
}
public Value getValue() { return value; }
}
public class Value {
private String stringValue = "ItsAValue";
private String doubleValue = 1.0d;
private String integerValue = 4321;
public void setStringValue(String value) {
stringValue = value;
}
}
And of course further classes with different attributes. E.g. this hierarchy was created and instantiated by Jaxb.
Now, i want to set a value in the Value-class. Of course i can execute something like:
TopLevel topLevel = new TopLevel();
topLevel.getMiddleLevel().getLowLevel().getValue().setStringValue("NewValue");
Is there a way to simplify or to generalize this, e.g. to be able to call the "path" through all these class-objects to set a value deep inside? Here is some pseudocode, what i mean:
public class Anotherclass {
public static void main(String[] args) {
TopLevel topLevel = new TopLevel();
setStringValueByPath("topLevel/middleLevel/lowLevel/value/stringValue", "newValue");
setDoubleValueByPath("topLevel/middleLevel/lowLevel/value/doubleValue", 5.0d);
setIntegerValueByPath("topLevel/middleLevel/lowLevel/value/integerValue", 1234);
}
}
Thanks a lot
Alex
Ok, if anyone is interested, i think i found a solution, that i was looking for:
A recursive approach based on Java.reflection :
public class ReflectionSetter {
private static List<Field> getFields(Object object) {
List<Field> fields = new ArrayList<>();
fields.addAll(Arrays.asList(object.getClass().getDeclaredFields()));
return fields;
}
private static Field hasField(Object object, String fieldName) {
for (Field f : getFields(object)) {
if (f.getName().equalsIgnoreCase(fieldName)) return f;
}
return null;
}
public static void setValue(Object object, String path, String newValue) throws IllegalArgumentException, IllegalAccessException {
if (path.contains("/")) {
int pos = path.indexOf('/');
String first = path.substring(0, pos);
String rest = path.substring(pos+1);
Field f = ReflectionSetter.hasField(object, first);
if (null == f) throw new IllegalArgumentException("Path not found: " + path);
f.setAccessible(true);
Object obj = f.get(object);
setValue(obj, rest, newValue);
} else {
Field f = ReflectionSetter.hasField(object, path);
if (f == null) throw new IllegalArgumentException("Field not found: " + path);
// if found -> set value
f.setAccessible(true);
f.set(object, newValue);
}
}
}
Now, you can set a value via a path. Usage:
TopLevel topLevel = new TopLevel();
ReflectionSetter.setValue(topLevel, "middleLevel/lowLevel/value/myValue", "NewValue");
An ideal efficient way to do this and by focussing more on reducing code complexity and at the same time improving code readability, you should look at design patterns, may be something like visitor pattern.
One of the most common use cases of visitor pattern is and as a result of separating algorithm and the data structure, comes with ability to add new operations to existing object structures without modifying said structures.
Moving on to a phase where "No, I want to look at string based approached as pointed in question". Apache commons library provides something called JxPath.
Unsure if you tried looking at JxPath ref.apache.jx.path
It offers simple interpreter of an expression language called XPath. JXPath applies XPath expressions to graphs of objects of all kinds
Picking an example from your question :
TopLevel topLevel = new TopLevel();
JXPathContext context = JXPathContext.newContext(topLevel);
context.setValue("middleLevel/lowLevel/value/stringValue", "newStringValue");

How to avoid reflection using enums

I recently asked a question about some code of mine where I used reflection and one who wanted to help me with this problem mentioned that I shouldn't use reflection like this and that there is a better way doing it.
So I have an Interface for searching in external Systems:
public interface ReferenceController {
public Map<String, ReferenceElement> searchElements(String searchField, List<String> searchItems, SystemStage systemStage) throws Exception;
public String getStateMapping(String value);
public Boolean isAvailable(SystemStage systemStage) throws Exception;
}
And I have an ENUM where I declare which external systems I have and how their class is named which uses this interface. So if any other programmer want's to implement a new external system he has only to fill the interface and put two values in this ENUM and tada it should work.
So the part where I used the reflection was
public static void performSingleSearch(ReferenceSystem referenceSystem, String searchField, List<String> searchValues, SystemStage systemStage) throws Exception {
if(!isAvailable(referenceSystem, systemStage)) return;
Map<String, ReferenceElement> result = new HashMap<>();
try {
Class<?> classTemp = Class.forName(referenceSystem.getClassname());
Method method = classTemp.getMethod("searchElements", String.class , List.class, SystemStage.class);
result = (Map<String, ReferenceElement>) method.invoke(classTemp.newInstance(), searchField, searchValues, systemStage);
} catch (Exception e) {
return;
}
if(result != null) orderResults(result, referenceSystem);
}
In the ENUM ther is a function getClassname, which answers with the fqcn.
The Enum looks like this:
public enum ReferenceSystem {
UCMDB (refSystems.ucmdb.UcmdbFunctions.class),
PROIPS (refSystems.proips.ProIPSFunctions.class),
KV (refSystems.kv.KvFunctions.class),
FISERVICE(refSystems.fiservice.FiServiceFunctions.class),
COMMAND (refSystems.command.CommandFunctions.class),
FII (refSystems.fii.FiiFunctions.class);
private Class<?> clazz;
private ReferenceSystem(Class<?> controllerClass) {
this.clazz = controllerClass;
}
public String displayName() {
ResourceBundle bundle = ResourceBundle.getBundle("EnumI18n", Locale.GERMAN);
return bundle.getString(toString());
}
public String localizedDisplayName(Locale locale) {
ResourceBundle bundle = ResourceBundle.getBundle("EnumI18n", locale);
return bundle.getString(toString());
}
public Class<?> getClassname() { return clazz; }
}
I've already altered it according to #jhamon 's answer.
But I get an error when I try
classTemp.newInstance().searchElemets(...)
Because it doesn't know about searchElemts().
So the other user here said there would be the possibility of implementing the interface into the enum and then I don't have to reflect.
Could anyone tell me how, because I don't know and I don't know where or what to search.
Thanks
It seems all your search engines have a common method searchElementsand it's defined in the interface
Knowing that, why not call this method directly, and not by looking for it first. -> no more reflection to find the method.
public interface ReferenceController {
public Map<String, ReferenceElement> searchElements(String searchField, List<String> searchItems, SystemStage systemStage) throws Exception;
public String getStateMapping(String value);
public Boolean isAvailable(SystemStage systemStage) throws Exception;
}
Instead of storing the class name as String in the Enum, store the .class -> no more reflection to find the class.
public static void performSingleSearch(ReferenceSystem referenceSystem, String searchField, List<String> searchValues, SystemStage systemStage) throws Exception {
if(!isAvailable(referenceSystem, systemStage)) return;
Map<String, ReferenceElement> result = new HashMap<>();
try {
Class<?> classTemp = referenceSystem.getClazz();
result = ((ReferenceController) classTemp.newInstance()).searchElements(searchField, searchValues, systemStage);
} catch (Exception e) {
return;
}
if(result != null) orderResults(result, referenceSystem);
}

Java indeterminate number of arguments of indeterminate type

My company has an application server that receives sets of instructions in their own bespoke XTML syntax. As this is limited, there's a special "drop to Java" command that sends arguments to a JVM (1.6.0_39). Arguments are passed as "in" only, or "in/out", where the special "in/out" variables are a library of mutables for use with this platform.
Previously the only way to receive external configuration was to use a different special command to read from an XTML file. For reasons not worth delving into, this method of configuration is difficult to scale, so I'm working on a way to do this with Java.
The syntax for this configuration was two-tuples of (String,T) where String was the property name in the XTML file, and T was the in/out mutable that the application server would assign the property value to.
I'm attempting to make this transition as seamless as possible, and not have to do annoying string parsing in the application server.
I already have a function
public String[] get(String ... keys)
That retrieves the values from the application servers' keys, but What I really need is a function
public static void get(T ... args)
that accepts the two-tuples. However, note it needs to be static in order to be called from the application server, and my understanding is that T can't be used in a static context.
I'm at a loss for how to approach this problem in a way that doesn't require (at least) two steps, and there is no way to loop over the arguments in the application server.
I know I'm working within a tight set of constraints here, so if the answer is "you have to some messed up stuff", that's fine - I'd just like any insight into another way.
-- edit --
Editing a more specific example.
The configuration is a set of key-value pairs, and can be in a database or a file. The get function is:
public JSONObject get(String ... keys) throws ClassNotFoundException, SQLException, KeyNotFoundException, FileNotFoundException, IOException {
JSONObject response = new JSONObject();
if(this.isDatabase) {
for(int i=0;i<keys.length;i++){
PreparedStatement statement = this.prepare("SELECT value FROM "+this.databaseSchema+"."+this.settingsTableName+" WHERE key = ? LIMIT 1");
statement.setString(1, keys[i]);
ResultSet results = statement.executeQuery();
boolean found = false;
while(results.next()){
String value = results.getString("value");
value = value.replace("\"","");
response.put(keys[i], value);
found = true;
}
if(!found){
throw new KeyNotFoundException(keys[i]);
}
}
} else if (this.isFile) {
boolean[] found = new boolean[keys.length];
BufferedReader br = new BufferedReader(new FileReader(this.settingsFile));
String line;
while((line = br.readLine()) != null ){
String key;
String value;
for(int i=0;i<line.length();i++){
if(line.charAt(i) == '='){
key = line.substring(0,i);
value = line.substring(i+1,line.length());
if(indexOfString(keys,key) != -1){
value = value.replace("\"","");
found[indexOfString(keys,key)] = true;
response.put(key,value);
if(allFound(found)==-1){
return response;
}
}
break;
}
}
}
if(allFound(found)!=-1){
throw new KeyNotFoundException(keys[allFound(found)]);
}
}
return response;
If I had my way, it would look like ...
// ConfigurationReader.java
public class ConfigurationReader{
public ConfigurationReader( ... ){}
public static JSONObject get(String key){
// Get the key
}
}
// ConfigurationInterface.java
public static void get(T ... args){
ConfigurationReader cfgReader = new ConfigurationReader( ... );
for(var i=0;i<args.length;i+=2){
in = args[i];
out = args[i+1];
out = cfgReader.get(in);
}
}
You can use generic types in a static context. Your question is somewhat vague/unclear about how you intend to do this, but consider the example below:
public class Example {
public static void main(String[] args) {
Type t1 = new Type("foo");
Type t2 = new Type("bar");
Type t3 = new Type("baz");
Printer.<Type> printNames(t1, t2, t3);
}
public static class Printer {
#SafeVarargs
public static <T extends Type> void printNames(T... objs) {
for (T obj : objs) {
System.out.println(obj);
}
}
}
public static class Type {
private final String name;
public Type(String name) {
this.name = name;
}
#Override
public final String toString() {
return name;
}
}
}
Printer.<Type> printNames(t1, t2, t3) makes a static reference to the printNames method, parameterized with the Type generic type.
Note that this is type-safe. Attempting to pass an object of a different type into that parameterized method will fail at compile-time (assuming the type is known to be different at that point):
Example.java:8: error: method printNames in class Printer cannot be applied to given types;
Printer.<Type> printNames(t1, t2, t3, "test");
^
required: T[]
found: Type,Type,Type,String
reason: varargs mismatch; String cannot be converted to Type
where T is a type-variable:
T extends Type declared in method <T>printNames(T...)
Edit
Based on your comment, the issue isn't that you're trying use a generic type for your method argument (in the Java-sense of the word generic, anyway); you're simply looking for any non-specific, parent class that both String and your custom type inherit from. There's only one such class: Object.
I'd strongly recommend reconsidering your design if you have any flexibility, since this will make for poor API design. However you can have your method accept an arbitrary number of arbitrarily-typed objects using Object... objs.
For example:
public class Example {
public static void main(String[] args) {
Printer.printNames("a", "b", new Type("foo"), new Type("bar"));
}
public static class Printer {
public static void printNames(Object... objs) {
for (Object obj : objs) {
if (obj instanceof String) {
System.out.println(((String) obj).toUpperCase());
}
else if (obj instanceof Type) {
System.out.println(obj);
}
}
}
}
public static class Type {
private final String name;
public Type(String name) { this.name = name; }
public final String toString() { return name; }
}
}
Based on #nbrooks work, I found a solution. I made a temporary MutableString (to be replaced by the classes provided by the library).
public static class MutableString {
public String value;
public MutableString(){}
}
// One for every mutable type
public static void Pair(String key, MutableString mutable, ApplicationConfiguration appConfig) throws Exception{
mutable.value = appConfig.get(key).toString();
}
public static void Retrieve(Object ... args) throws Exception {
ApplicationConfiguration appConfig = new ApplicationConfiguration( ##args## );
for(int i=0;i<args.length;i+=2){
if(args[i+1].getClass().equals(new MutableString().getClass())){
ApplicationConfiguration.Pair( (String) args[i], (MutableString) args[i+1], appConfig);
} // One for every mutable type
}
}

How would I iterate through a list of [[tokens]] and replace them with textbox input?

Here is the basic code i'm trying to make work:
Field fields[] = SalesLetter.class.getDeclaredFields();
String fieldName;
for (int j = 0, m = fields.length; j < m; j++) {
fieldName = fields[j].getName(); //example fieldname [[headline]]
templateHTML = templateHTML.replace(fieldName, Letter.fieldName());
}
I believe I'm going about it wrong by trying to getDeclaredFields (which isn't even syntactically correct). When I finished my title, it came up with a few other stackoverflow questions which I read before writing this. They were:
Best way to replace tokens in a large text template
Replacing tokens in a string from an array
It gave me the idea of reading all legal [[tokens]] from a text file, putting them into a hash (err I mean map, this is java :D), then creating an object reference with the same name as that token.
I can't figure out how I would do such a thing in java specifically, or if that would work. Please assist.
Thanks in advance,
Cody Goodman
Note: I'm trying to make everything as flexible as possible, so maybe in the future I could add things such as "[[tokenname]]:this is token name, you need to really think about what the customer wants to come up with a good token name" in a text file, then those fields are generated on my form, and everything works :)
In order to read values from non-static fields of a type, you'll need a reference to an instance of the type:
public class ReflectFields {
static class Letter {
public int baz = 100;
}
static class SalesLetter extends Letter {
public String foo = "bar";
}
public static void main(String[] args) throws Exception {
// TODO: better exception handling, etc.
SalesLetter instance = new SalesLetter();
for (Field field : instance.getClass().getFields()) {
System.out.format("%s = %s%n", field.getName(), field.get(instance));
}
}
}
You'll also have to watch for private fields, etc. In general, this approach should be avoided as it breaks encapsulation by looking at class internals.
Consider using the JavaBean API.
public class BeanHelper {
private final Object bean;
private final Map<String, Method> getters = new TreeMap<String, Method>();
public BeanHelper(Object bean) {
this.bean = bean;
for (PropertyDescriptor pd : Introspector.getBeanInfo(bean.getClass(),
Object.class).getPropertyDescriptors()) {
getters.put(pd.getName(), pd.getReadMethod());
}
}
public Set<String> getProperties() { return getters.keySet(); }
public Object get(String propertyName) {
return getters.get(propertyName).invoke(bean);
}
public static void main(String[] args) {
BeanHelper helper = new BeanHelper(new MyBean());
for (String prop : helper.getProperties()) {
System.out.format("%s = %s%n", prop, helper.get(prop));
}
}
public static class MyBean {
private final String foo = "bar";
private final boolean baz = true;
public String getFoo() { return foo; }
public boolean isBaz() { return baz; }
}
}
Exception handling has been omitted for brevity, so you'll need to add some try/catch blocks (I suggest wrapping the caught exceptions in IllegalStateExceptions).
What about using a template engine like Freemarker, Velocity or StringTemplate:
replace [[ by ${ and ]] by }
create a model from a properties file containing the replacements
process templateHTML
Here an example with Freemarker (without Exception handling)
Configuration config = new Configuration();
StringTemplateLoader loader = new StringTemplateLoader();
config.setTeplateLoader(loader);
Map model = Properites.load(new FileInputStream("tokens.properties"));
loader.putTemplate("html.ftl", templateHTML);
Template template = config.getTemplate("html.ftl");
Writer out = new StringWriter();
template.process(root, out);
String result = out.toString();
StringTemplate may be more simple (replace [[ and ]] by $), but I am not fimilar with it:
Map model = Properites.load(new FileInputStream("tokens.properties"));
StringTemplate template = new StringTemplate(templateHTML);
template.setAttributes(model);
String result = template.toString();
The tokens.properties file looks like:
tokenname:this is token name

Categories