001package ca.uhn.hl7v2.util;
002
003import java.io.BufferedReader;
004import java.io.File;
005import java.io.FileFilter;
006import java.io.FileReader;
007import java.io.FilenameFilter;
008import java.io.IOException;
009import java.util.HashMap;
010import java.util.Map;
011import java.util.StringTokenizer;
012
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016import ca.uhn.hl7v2.ErrorCode;
017import ca.uhn.hl7v2.HL7Exception;
018
019/**
020 * <p>Implements CodeMapper using files to store code values.  Files are arranged
021 * in the following directory structure.  The base directory is called "codemap".
022 * This should be created under the hapi.home directory (see Home class; defaults to .).
023 * Under the base directory, there should be one directory for each interface, and
024 * each of these directories should be named after the interface.  For example if you
025 * had interfaces to pharmacy and lab systems you might have the following directories:</p>
026 * <p> <hapi.home>/codemap/pharmacy<br>
027 * <hapi.home>/codemap/lab</p>
028 * <p>Each directory should contain two files per HL7 table, named after the table numbers as
029 * follows: "hl7nnnn.li" and "hl7nnnn.il", where nnnn is the 4 digit table number.  The .il
030 * file contains maps from interface codes to local codes, and the .li file contains maps from
031 * local codes to interface codes (these unfortunately may not be symmetrical).</p>
032 * <p>Each line of a file contains a single code map, with the "from" value and the "to" value
033 * separated by a tab.  For example, the file <hapi.home>/lab/HL70001.li (to map local codes to interface
034 * codes for the HL7 admnistrative sex table in your lab system interface) might contain the
035 * following line: </p>
036 * <p>male&tab;M</p>
037 * <p>This means that the local code "male" maps to the interface code "M".</p>
038 * <p>Lines that start with "//" are treated as comments.</p>
039 * @author Bryan Tripp
040 */
041public class FileCodeMapper extends CodeMapper {
042
043    private static final Logger log = LoggerFactory.getLogger(FileCodeMapper.class);
044
045    private boolean throwIfNoMatch = false;
046    File baseDir;
047    private Map<String, Map<String, Map<String, String>>> interfaceToLocal;
048    private Map<String, Map<String, Map<String, String>>> localToInterface;
049
050    /**
051     * Creates a new instance of FileCodeMapper.  You should probably not
052     * construct a FileCodeMapper directly.  Use CodeMapper.getInstance()
053     * instead ... this will ensure that only a single instance is created,
054     * which is important for performance because code maps are loaded from
055     * disk every time this constructor is called.
056     */
057    public FileCodeMapper() throws HL7Exception {
058        baseDir = new File(Home.getHomeDirectory().getAbsolutePath() + "/codemap");
059        refreshCache();
060    }
061
062    /**
063     * If values are cached in such a way that they are not guaranteed to be current, a call
064     * to this method refreshes the values.
065     */
066    public void refreshCache() throws HL7Exception {
067        localToInterface = new HashMap<String, Map<String, Map<String, String>>>(10);
068        interfaceToLocal = new HashMap<String, Map<String, Map<String, String>>>(10);
069
070        log.info("Refreshing cache");
071
072        try {
073            //get list of child directories
074            File[] interfaceDirs = this.baseDir.listFiles(new FileFilter() {
075                public boolean accept(File pathname) {
076                    boolean acc = false;
077                    if (pathname.isDirectory())
078                        acc = true;
079                    return acc;
080                }
081            });
082
083            //loop through directories and set up maps
084            for (int i = 0; i < interfaceDirs.length; i++) {
085
086                log.info(
087                    "Checking directory {} for interface code maps.", interfaceDirs[i].getName());
088
089                //get list of .li (local -> interface) and .il (interface -> local) files
090                File[] mapFiles = interfaceDirs[i].listFiles(new FilenameFilter() {
091                    public boolean accept(File dir, String name) {
092                        boolean acc = false;
093                        if (name.toUpperCase().startsWith("HL7")) {
094                            if (name.substring(name.lastIndexOf('.')).equals(".li")
095                                || name.substring(name.lastIndexOf('.')).equals(".il"))
096                                acc = true;
097                        }
098                        return acc;
099                    }
100                });
101
102                //read map entries from each file and add to hash maps for li and il codes
103                HashMap<String, Map<String, String>> li = new HashMap<String, Map<String, String>>(50);
104                HashMap<String, Map<String, String>> il = new HashMap<String, Map<String, String>>(50);
105                for (int j = 0; j < mapFiles.length; j++) {
106                    log.info("Reading map entries from file {}", mapFiles[j]);
107
108                    String fName = mapFiles[j].getName();
109                    String tableName = fName.substring(0, fName.lastIndexOf('.'));
110                    String mapDirection = fName.substring(fName.lastIndexOf('.') + 1);
111
112                    //read values and store in HashMap
113                        Map<String, String> codeMap = new HashMap<String, String>(25);
114                    BufferedReader in = null;
115                    try {
116                                in = new BufferedReader(new FileReader(mapFiles[j]));
117                                while (in.ready()) {
118                                    String line = in.readLine();
119                                    if (!line.startsWith("//")) {
120                                        StringTokenizer tok = new StringTokenizer(line, "\t", false);
121                                        String from = null;
122                                        String to = null;
123                                        if (tok.hasMoreTokens())
124                                            from = tok.nextToken();
125                                        if (tok.hasMoreTokens())
126                                            to = tok.nextToken();
127                                        if (from != null && to != null)
128                                            codeMap.put(from, to);
129                                    }
130                                }
131                    }
132                    finally {
133                        if (in != null) in.close();
134                    }
135
136                    //add to appropriate map for this interface
137                    if (mapDirection.equals("il")) {
138                        il.put(tableName.toUpperCase(), codeMap);
139                        log.debug("Adding {} codes to interface -> local map for {} in {} interface",
140                                new Object[] {codeMap.size(), tableName, interfaceDirs[i].getName()});
141                    }
142                    else {
143                        li.put(tableName.toUpperCase(), codeMap);
144                        log.debug("Adding {} codes to local -> interface map for {} in {} interface",
145                                new Object[] {codeMap.size(), tableName, interfaceDirs[i].getName()});
146                    }
147                }
148
149                //add maps for this interface (this directory) to global list
150                interfaceToLocal.put(interfaceDirs[i].getName(), il);
151                localToInterface.put(interfaceDirs[i].getName(), li);
152            }
153
154        }
155        catch (IOException e) {
156            throw new HL7Exception(
157                "Can't read interface code maps from disk", e);
158        }
159    }
160
161    /**
162     * Returns the local code for the given interface code as it appears in
163     * the given interface.
164     */
165    public String getLocalCode(String interfaceName, int hl7Table, String interfaceCode) throws HL7Exception {
166        String localCode = null;
167        try {
168            Map<String, Map<String, String>> interfaceMap = interfaceToLocal.get(interfaceName);
169            localCode = getCode(interfaceMap, hl7Table, interfaceCode);
170        }
171        catch (NullPointerException npe) {
172            if (this.throwIfNoMatch)
173                throw new HL7Exception(
174                    "No local mapping for the interface code "
175                        + interfaceCode
176                        + " for HL7 table "
177                        + hl7Table
178                        + " for the interface '"
179                        + interfaceName
180                        + "'",
181                    ErrorCode.TABLE_VALUE_NOT_FOUND);
182        }
183        return localCode;
184    }
185
186    /**
187     * Common code for getLocalcode and getInterfaceCode
188     */
189    private String getCode(Map<String, Map<String, String>> interfaceMap, int hl7Table, String code) {
190        String ret = null;
191
192        //get map for the given table
193        StringBuffer tableName = new StringBuffer();
194        tableName.append("HL7");
195        if (hl7Table < 1000)
196            tableName.append("0");
197        if (hl7Table < 100)
198            tableName.append("0");
199        if (hl7Table < 10)
200            tableName.append("0");
201        tableName.append(hl7Table);
202        Map<String, String> tableMap = interfaceMap.get(tableName.toString());
203
204        //get code
205        ret = tableMap.get(code).toString();
206        return ret;
207    }
208
209    /**
210     * Returns the interface code for the given local code, for use in the context
211     * of the given interface.
212     */
213    public String getInterfaceCode(String interfaceName, int hl7Table, String localCode) throws HL7Exception {
214        String interfaceCode = null;
215        try {
216            Map<String, Map<String, String>> interfaceMap = localToInterface.get(interfaceName);
217            interfaceCode = getCode(interfaceMap, hl7Table, localCode);
218        }
219        catch (NullPointerException npe) {
220            if (this.throwIfNoMatch)
221                throw new HL7Exception(
222                    "No interface mapping for the local code "
223                        + localCode
224                        + " for HL7 table "
225                        + hl7Table
226                        + " for the interface '"
227                        + interfaceName
228                        + "'",
229                    ErrorCode.TABLE_VALUE_NOT_FOUND);
230        }
231        return interfaceCode;
232    }
233
234    /**
235     * Determines what happens if no matching code is found during a lookup.  If set to true,
236     * an HL7Exception is thrown if there is no match.  If false, null is returned.  The default
237     * is false.
238     */
239    public void throwExceptionIfNoMatch(boolean throwException) {
240        this.throwIfNoMatch = throwException;
241    }
242
243    /**
244     * Test harness.
245     */
246    public static void main(String args[]) {
247        try {
248            //FileCodeMapper mapper = new FileCodeMapper();
249            CodeMapper.getInstance().throwExceptionIfNoMatch(true);
250            System.out.println("Local code for M is " + CodeMapper.getLocal("test", 1, "M"));
251            System.out.println("Interface code for female is " + CodeMapper.getInt("test", 1, "female"));
252
253        }
254        catch (HL7Exception e) {
255            e.printStackTrace();
256        }
257    }
258
259}