Question

What I want to do is load key/value pairs from a file (excel file using Apache poi) into a static map that will be used as a lookup table. Once loaded the table will not change.

public final class LookupTable
{  
   private final static Map<String, String> map;  
   static {
     map = new HashMap<String, String>();
     // should do initialization here
     // InputStream is = new FileInputStream(new File("pathToFile"));
     // not sure how to pass pathToFile without hardcoding it?
   }

   private LookupTable() {
   }

  public static void loadTable(InputStream is) {
    // read table from file
    // load it into map
    map.put("regex", "value");
  }

  public static String getValue(String key) {
    return map.get(key);
  }
}

Ideally I want to load the map within the static initialization block, but how would I pass the stream in without hard coding it? The problem I see using the loadTable static method is it might not be called before calling the other static methods.

// LookupTable.loadTable(stream);  
LookupTable.getValue("regex"); // null since map was never populated.

Is there a better approach to this?

Was it helpful?

Solution 3

You cannot pass information into static initialization blocks - they are supposed to work in isolation. Since the stream that you are planning to pass needs to be known before the program begins execution, presumably your LookupTable should be able to find it too. For example, this could be some sort of configuration utility that provides the stream for you. Then you can write your initializer like this:

static {
    InputStream exelStream = MyConfigUtil.getExcelStreamForLookup();
    loadTable(exelStream);
}

Presumably, there is a class in the system that could get your Excel stream from a source that is known to it. The source does not need to be hard-coded: it could read the location from a configuration file, or receive the data from a predefined network location on your server. In all cases the process of getting the Excel stream has to "bottom out" somewhere, in the sense that something in your system needs to be able to find it without additional parameters.

OTHER TIPS

Anything you use will have to be accessible at startup. As far as I know, your options are:

  1. Hard-code the path. This is bad for obvious reasons.
  2. A static variable or static method. This presents a bit of a chicken-and-egg problem; ultimately it gets hard-coded, but at least you can do a search with a static method.
  3. Use a variable, either Java or Environment. So, you'd use something System.getProperty("filename", "/default/filename"). Better because it's at least customizable using the environment or -D parameters at JVM startup.
  4. Use the ClassLoader getResource* methods. This is probably The Right Answer. Specifically, you'll probably want to use the getResourceAsStream() method on the current thread's context ClassLoader Thread.currentThread().getContextClassLoader(). (So, Thread.currentThread().getContextClassLoader().getResourceAsStream("filename") in total.) The ClassLoader will then find your resource for you (as long as you put it somewhere sane in your CLASSPATH).

Yes, there is a better approach, use the Factory design pattern to initialise the object before you have to use it:

http://www.oodesign.com/factory-pattern.html

This is not directly answering your question, but I don't see why map has to be static. You could change map to non-static and change the constructor to public LookupTable(File file) {...fill map...}. You could then even have many instances of that class if you have different excel files; it might not be the case now, but it would "future-proof" your code.

This is probably a case for using lazy loading of the map.

But you will need to set the inputFileName before calling getValue() the first time. This would be done in your initialization code for the applications. (Or you could have a static method to set it.)

This points out the advantage of lazy loading. You don't have to have the file name available until you call getValue() the first time. With a static initializer, you have to get the file name stored somewhere outside the class so it can be used to load the data when the class loads (but after the static fields have been initialized.

 public static String inputFileName = null;

 public static String getValue(String key) {
    if (map == null) {
        map = = new HashMap<String, String>();
        // open the file using 'inputFileName'
        loadTable(InputStream is);
    }
    return map.get(key);
  }

If your code is multithreaded, let me know and I'll comment on the synchronization issues.

Alternate

You could also use Spring to inject the map and build it in some other class -- MapBuilder for example.

Try using System.getProperty() and pass parameter with -D in command line.

static String prop;

static {
    prop = System.getProperty("java.home");
}

public static void main(String... args) {

    System.out.println(prop);
}

I would suggest a singleton enum approach if it suits your case.

public enum LookupTable {

    INSTANCE(FileManager.getFileName());

    LookupTable(String fileName){
        props = new HashMap<String,String>();
        //Read from excel and fill the hashmap
    }

    private final Map<String, String> props;

    public String getMapValue(String key){
        return props.get(key);
    }   
}

Which can be called by

LookupTable.INSTANCE.getMapValue("mykey");

This will call these methods in order

  • Get filename from a filemanager class, which is parametrized on your needs
  • Call the constructor (it is private) and load properties from the excel file
  • getMapValue for the key and return

A subsequent call LookupTable.INSTANCE.getMapValue("mysecondkey") will only call getMapValue as the INSTANCE is initialized beforehand.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top