Issue #2915: Rewrite TextDBStaticData class.

Change-Id: I078f9c4a35e0477ee1248bb66f449d7fe4b165a1

Former-commit-id: 6124d62120 [formerly b27d83cc5123d08de450a45edb40ea722fc95c58]
Former-commit-id: 9f50f29a67
This commit is contained in:
David Gillingham 2014-04-01 13:24:48 -05:00
parent 27d2911801
commit c899410f7b
11 changed files with 470 additions and 459 deletions

View file

@ -39,16 +39,19 @@ import com.raytheon.uf.edex.wmo.message.WMOHeader;
* TODO Add Description
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 12, 2008 jkorman Initial creation
* Jul 10, 2009 2191 rjpeter Finished implementation.
* 06/29/2012 15154 D. Friedman Fix detection of TAF collectives.
* ======================================
* AWIPS2 DR Work
* 07/25/2012 959 jkorman Modified order of entry for determining
* the data type (standard or collective) for input data.
* Jun 29, 2012 15154 D. Friedman Fix detection of TAF collectives.
* Jul 25, 2012 959 jkorman Modified order of entry for determining
* the data type (standard or collective)
* for input data.
* Apr 01, 2014 2915 dgilling Support re-factored TextDBStaticData.
*
* </pre>
*
* @author jkorman
@ -63,9 +66,6 @@ public class TextSeparatorFactory {
private static final String siteId = PropertiesFactory.getInstance()
.getEnvProperties().getEnvValue("SITENAME");
private static final TextDBStaticData staticData = TextDBStaticData
.instance(siteId);
private static Pattern TAF_PTRN = Pattern.compile("^TAF\\s*$");
/**
@ -199,11 +199,11 @@ public class TextSeparatorFactory {
// Maintain this order of entry so that Standard Text products
// are checked before collectives.
if ((stdAfosId = staticData.getProductId(ispanId)) != null) {
if ((stdAfosId = TextDBStaticData.getProductId(ispanId)) != null) {
msgType = WMOMessageType.STD_TEXT;
} else if (staticData.matchStdCollective(dataDes) != null) {
} else if (TextDBStaticData.matchStdCollective(dataDes) != null) {
msgType = WMOMessageType.STD_COLLECTIVE;
} else if (staticData.matchUACollective(dataDes) != null) {
} else if (TextDBStaticData.matchUACollective(dataDes) != null) {
msgType = WMOMessageType.UA_COLLECTIVE;
}
@ -211,8 +211,8 @@ public class TextSeparatorFactory {
if (msgType == null) {
if (!hdr.startsWith("SXUS70")
&& !hdr.startsWith("FRUS45")
&& (firstLineLen > 6 ||
TAF_PTRN.matcher(firstLine).matches())
&& (firstLineLen > 6 || TAF_PTRN.matcher(firstLine)
.matches())
&& (hdr.startsWith("SA") || hdr.startsWith("SP")
|| hdr.startsWith("FR") || hdr
.startsWith("FT"))) {
@ -237,7 +237,7 @@ public class TextSeparatorFactory {
if (WMOMessageType.STD_TEXT.equals(msgType)
&& stdAfosId != null) {
if (staticData.isExcluded(stdAfosId)) {
if (TextDBStaticData.isExcluded(stdAfosId)) {
logger.debug("NCF_ENTRY " + ispanId + " is skipped");
}
}

View file

@ -27,6 +27,7 @@ import org.apache.commons.logging.LogFactory;
import com.raytheon.edex.esb.Headers;
import com.raytheon.edex.plugin.text.impl.TextSeparatorFactory;
import com.raytheon.edex.textdb.dbapi.impl.TextDBStaticData;
import com.raytheon.edex.textdb.dbapi.impl.WMOReportData;
import com.raytheon.uf.common.site.SiteMap;
import com.raytheon.uf.edex.wmo.message.AFOSProductId;
@ -46,6 +47,7 @@ import com.raytheon.uf.edex.wmo.message.WMOHeader;
* Mar 06, 2014 2652 skorolev Corrected rawMsg extraction.
* Mar 14, 2014 2652 skorolev Changed logging for skipped headers.
* Fixed calculation of message end.
* Apr 01, 2014 2915 dgilling Support re-factored TextDBStaticData.
* </pre>
*
* @author jkorman
@ -92,7 +94,8 @@ public class StdCollectiveSeparator extends WMOMessageSeparator {
protected void createProductId() {
WMOHeader wmoHeader = getWmoHeader();
String hdr = wmoHeader.getWmoHeader();
String afosId = staticData.matchStdCollective(createDataDes(wmoHeader));
String afosId = TextDBStaticData
.matchStdCollective(createDataDes(wmoHeader));
if (afosId != null) {
// String ccc = SiteMap.getInstance().getCCCFromXXXCode(siteId);
@ -269,8 +272,9 @@ public class StdCollectiveSeparator extends WMOMessageSeparator {
// filter out junk characters
while (buffer.length() > 0
&& !checkCharNum(buffer.charAt(0)))
&& !checkCharNum(buffer.charAt(0))) {
buffer.deleteCharAt(0);
}
// again, trash data if it is less than 20 bytes
if (buffer.length() < MIN_COLL_DATA_LEN) {
@ -444,8 +448,9 @@ public class StdCollectiveSeparator extends WMOMessageSeparator {
} else if (buffer.charAt(0) == (char) 0x1e) {
parsedMsg.setLength(parsedMsg.length() - 3);
} else if (buffer.charAt(0) == '=') {
if (safeStrpbrk(buffer, CSPL))
if (safeStrpbrk(buffer, CSPL)) {
buffer.deleteCharAt(0);
}
} else if ((buffer.charAt(0) == EOM)
&& (parsedMsg.length() > (MIN_COLL_DATA_LEN - 1))) {
checkFouHeader = true;
@ -458,9 +463,10 @@ public class StdCollectiveSeparator extends WMOMessageSeparator {
if ((reportType != null)
&& (reportType.equals("METAR") || reportType.equals("SPECI")
|| reportType.equals("TESTM") || reportType
.equals("TESTS")))
.equals("TESTS"))) {
parsedMsg.insert(0, reportType + " ");
}
}
/**
* Gets the CCC from XXX map from national table and combines this with the
@ -483,20 +489,18 @@ public class StdCollectiveSeparator extends WMOMessageSeparator {
// If this is a national bit product, then CCC = CCC of the current
// site.
if ("AAA".equals(afos_id.getCcc()))
if ("AAA".equals(afos_id.getCcc())) {
CCC_id = SiteMap.getInstance().getCCCFromXXXCode(siteId);
// Otherwise, use the national category table to get the CCC from the
// XXX
else if ((CCC_id = staticData.mapICAOToCCC(XXX_id.toString())) == null) {
} else if ((CCC_id = TextDBStaticData.mapICAOToCCC(XXX_id.toString())) == null) {
// We failed to get a CCC from the national_category_table...
// If the XXX is 3 characters, and the origin starts with K, try
// prepending K or P (the latter for AK, HI products)
if (trimmedXXX.length() == 3 && origin.startsWith("K")) {
newId = "K" + trimmedXXX;
if ((CCC_id = staticData.mapICAOToCCC(newId)) == null) {
if ((CCC_id = TextDBStaticData.mapICAOToCCC(newId)) == null) {
newId = "P" + trimmedXXX;
if ((CCC_id = staticData.mapICAOToCCC(newId)) == null) {
if ((CCC_id = TextDBStaticData.mapICAOToCCC(newId)) == null) {
// logger.error("NCF_FAIL to map XXX to CCC: " +
// XXX_id);
subHeadersSkipped
@ -514,7 +518,7 @@ public class StdCollectiveSeparator extends WMOMessageSeparator {
// character of the origin.
else if (trimmedXXX.length() == 3) {
newId = origin.charAt(0) + trimmedXXX;
if ((CCC_id = staticData.mapICAOToCCC(newId)) == null) {
if ((CCC_id = TextDBStaticData.mapICAOToCCC(newId)) == null) {
subHeadersSkipped
.put(newWmoHdr,
"Product "
@ -533,7 +537,7 @@ public class StdCollectiveSeparator extends WMOMessageSeparator {
+ " is excluded from storage due to incorrect xxxid"
+ XXX_id);
return false;
} else
} else {
// If trimmedXXX has 4 characters and not found in
// national_category_table.template.
subHeadersSkipped
@ -543,6 +547,7 @@ public class StdCollectiveSeparator extends WMOMessageSeparator {
+ " is excluded from storage due to "
+ trimmedXXX
+ " not present in national_category_table.template");
}
return false;
}
}
@ -554,10 +559,11 @@ public class StdCollectiveSeparator extends WMOMessageSeparator {
product_id.setNnn(afos_id.getNnn());
// Put all three of the id pieces together.
if (trimmedXXX.length() == 3)
if (trimmedXXX.length() == 3) {
product_id.setXxx(trimmedXXX);
else
} else {
product_id.setXxx(XXX_id.substring(1, 4));
}
return true;
}
}

View file

@ -42,6 +42,7 @@ import com.raytheon.uf.edex.wmo.message.WMOHeader;
* Jul 26, 2011 10043 rferrel Modified identifyReports to
* have checks like A1.
* Mar 13, 2014 2652 skorolev Fixed calculation of message end.
* Apr 01, 2014 2915 dgilling Support re-factored TextDBStaticData.
* </pre>
*
* @author jkorman
@ -69,7 +70,7 @@ public class StdTextSeparator extends WMOMessageSeparator {
@Override
protected void createProductId() {
String ispanId = createIspanId(getWmoHeader());
String afosId = staticData.getProductId(ispanId);
String afosId = TextDBStaticData.getProductId(ispanId);
if (afosId != null) {
productId = new AFOSProductId(afosId);
@ -87,11 +88,11 @@ public class StdTextSeparator extends WMOMessageSeparator {
WMOHeader wmoHeader = getWmoHeader();
String ispanId = createIspanId(wmoHeader);
String product_id = TextDBStaticData.instance(siteId).getProductId(
ispanId);
String product_id = TextDBStaticData.getProductId(ispanId);
// check whether to exclude from decoding for storage
if (product_id != null && staticData.isExcluded(product_id.toString())) {
if (product_id != null
&& TextDBStaticData.isExcluded(product_id.toString())) {
logger.debug("NCF_ENTRY " + product_id.toString() + " is skipped");
return;
}
@ -196,7 +197,7 @@ public class StdTextSeparator extends WMOMessageSeparator {
}
// check whether incoming product is excluded from storage
if (staticData.isExcluded(product_id.toString())) {
if (TextDBStaticData.isExcluded(product_id.toString())) {
logger.debug("NCF_ENTRY " + product_id.toString() + " is skipped");
return true;
}
@ -354,7 +355,7 @@ public class StdTextSeparator extends WMOMessageSeparator {
// table.
if (stdFlg) {
logger.trace("Mapping from stdflag.");
newId = staticData.getProductId(ispanId);
newId = TextDBStaticData.getProductId(ispanId);
if (newId == null) {
// logger.error("Unable to create AFOS id - ispan table.");
return false;
@ -368,7 +369,7 @@ public class StdTextSeparator extends WMOMessageSeparator {
// Check to see if the product is a non-collective METAR (out of
// country), and map against the ISPAN table to get the id.
newId = staticData.getProductId(ispanId);
newId = TextDBStaticData.getProductId(ispanId);
if (newId == null) {
// logger.error("Unable to create AFOS id - ispan table");
return false;
@ -381,16 +382,16 @@ public class StdTextSeparator extends WMOMessageSeparator {
// Try to map the nnnId with the bit table for a national bit product;
// otherwise, check the AFOS table for the ccc value of the origin.
newId = staticData.getSiteIdFromNNN(nnnId);
newId = TextDBStaticData.getSiteIdFromNNN(nnnId, siteId);
if (newId == null) {
cccId = staticData.getAFOSTableMap(origin);
cccId = TextDBStaticData.getAFOSTableMap(origin);
if (cccId == null) {
if (nnnxxx.length() >= 6) {
cccId = staticData.getAFOSTableMap("K"
cccId = TextDBStaticData.getAFOSTableMap("K"
+ nnnxxx.substring(3, 6));
} else if (nnnxxx.length() > 3) {
cccId = staticData.getAFOSTableMap("K"
cccId = TextDBStaticData.getAFOSTableMap("K"
+ nnnxxx.substring(3));
}
if (cccId == null) { // KWBC RCM,VER
@ -421,7 +422,7 @@ public class StdTextSeparator extends WMOMessageSeparator {
if ((newId.length() < 7) || (newId.length() > 9)) {
// If the length is bad, try mapping to the ispan table with the
// ispan id.
newId = staticData.getProductId(ispanId);
newId = TextDBStaticData.getProductId(ispanId);
if (newId == null) {
// logger.error("Unable to create AFOS id - ispan table");
return false;
@ -445,7 +446,7 @@ public class StdTextSeparator extends WMOMessageSeparator {
}
}
if (badflag) {
newId = staticData.getProductId(ispanId);
newId = TextDBStaticData.getProductId(ispanId);
if (newId == null) {
// logger.error("Invalid product - ispan table.");
return false;
@ -458,7 +459,7 @@ public class StdTextSeparator extends WMOMessageSeparator {
}
}
} else {
newId = staticData.getProductId(ispanId);
newId = TextDBStaticData.getProductId(ispanId);
if (newId == null) {
// logger.error("Invalid product - ispan table.");
return false;

View file

@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory;
import com.raytheon.edex.esb.Headers;
import com.raytheon.edex.plugin.text.impl.TextSeparatorFactory;
import com.raytheon.edex.textdb.dbapi.impl.TextDBStaticData;
import com.raytheon.edex.textdb.dbapi.impl.WMOReportData;
import com.raytheon.uf.edex.wmo.message.AFOSProductId;
import com.raytheon.uf.edex.wmo.message.WMOHeader;
@ -43,6 +44,7 @@ import com.raytheon.uf.edex.wmo.message.WMOHeader;
* Sep 3, 2008 jkorman Initial creation
* Jul 10, 2009 2191 rjpeter Reimplemented.
* Mar 13, 2014 2652 skorolev Fixed calculation of message end.
* Apr 01, 2014 2915 dgilling Support re-factored TextDBStaticData.
* </pre>
*
* @author jkorman
@ -88,7 +90,8 @@ public class UACollectiveSeparator extends WMOMessageSeparator {
protected void createProductId() {
WMOHeader wmoHeader = getWmoHeader();
String hdr = wmoHeader.getWmoHeader();
String afosId = staticData.matchUACollective(createDataDes(wmoHeader));
String afosId = TextDBStaticData
.matchUACollective(createDataDes(wmoHeader));
if (afosId != null) {
productId = new AFOSProductId(afosId);
@ -162,7 +165,8 @@ public class UACollectiveSeparator extends WMOMessageSeparator {
}
// Get the XXX from the station number, using station_table.dat
else {
XXX_id = staticData.mapWMOToICAO(stationNum.toString());
XXX_id = TextDBStaticData.mapWMOToICAO(stationNum
.toString());
if (XXX_id == null) {
logger.debug("NCF_FAIL to map station number to XXX: "
+ stationNum);
@ -339,11 +343,11 @@ public class UACollectiveSeparator extends WMOMessageSeparator {
uaNNN = "SGL";
}
String icao = staticData.mapWMOToICAO(wmoId);
String icao = TextDBStaticData.mapWMOToICAO(wmoId);
if (icao != null) {
// get the associated CCC
String cccId = staticData.mapICAOToCCC(icao);
String cccId = TextDBStaticData.mapICAOToCCC(icao);
AFOSProductId prodId = data.getAfosProdId();
if (prodId != null) {
@ -411,18 +415,18 @@ public class UACollectiveSeparator extends WMOMessageSeparator {
}
// Check for USUS80 or 90 formatted messages and decode
else if (dataDes.endsWith("80") || dataDes.endsWith("90")) {
if (checkCharNum(buffer.charAt(0)))
if (checkCharNum(buffer.charAt(0))) {
buffer.deleteCharAt(0);
else {
} else {
stationNum.append(assignTextSegment(buffer.toString(), CSPC));
}
getTextSegment(buffer, parsedMsg, CSEP);
} else {
// Otherwise it's standard format so decode
if (!checkCharNum(buffer.charAt(0)))
if (!checkCharNum(buffer.charAt(0))) {
buffer.deleteCharAt(0);
else {
} else {
// Move to the third field of the message to get the station
// number.
@ -449,9 +453,9 @@ public class UACollectiveSeparator extends WMOMessageSeparator {
// Remove excess control characters
if (buffer.length() == 0) {
if (parsedMsg.length() < MIN_COLL_DATA_LEN)
if (parsedMsg.length() < MIN_COLL_DATA_LEN) {
stationNum.setLength(0);
else {
} else {
trim_message(parsedMsg);
}
} else if (buffer.charAt(0) == '=') {
@ -489,7 +493,7 @@ public class UACollectiveSeparator extends WMOMessageSeparator {
// Otherwise, use the national category table to get the CCC from the
// XXX
else {
CCC_id = staticData.mapICAOToCCC(XXX_id);
CCC_id = TextDBStaticData.mapICAOToCCC(XXX_id);
if (CCC_id == null) {
// We failed to get a CCC from the national_category_table...
@ -497,10 +501,10 @@ public class UACollectiveSeparator extends WMOMessageSeparator {
// prepending K or P (the latter for AK, HI products)
if ((XXX_id.length() == 3) && (origin.charAt(0) == 'K')) {
newId = "K" + XXX_id;
CCC_id = staticData.mapICAOToCCC(newId);
CCC_id = TextDBStaticData.mapICAOToCCC(newId);
if (CCC_id == null) {
newId = "P" + XXX_id;
CCC_id = staticData.mapICAOToCCC(newId);
CCC_id = TextDBStaticData.mapICAOToCCC(newId);
if (CCC_id == null) {
// logger.error("NCF_FAIL to map XXX to CCC: "
// + XXX_id);
@ -513,7 +517,7 @@ public class UACollectiveSeparator extends WMOMessageSeparator {
// character of the origin.
else if (XXX_id.length() == 3) {
newId = origin.charAt(0) + XXX_id;
CCC_id = staticData.mapICAOToCCC(newId);
CCC_id = TextDBStaticData.mapICAOToCCC(newId);
if (CCC_id == null) {
// logger.error("NCF_FAIL to map XXX to CCC: " +
// XXX_id);

View file

@ -30,7 +30,6 @@ import org.apache.commons.logging.LogFactory;
import com.raytheon.edex.esb.Headers;
import com.raytheon.edex.plugin.AbstractRecordSeparator;
import com.raytheon.edex.textdb.dbapi.impl.TextDBStaticData;
import com.raytheon.edex.textdb.dbapi.impl.WMOReportData;
import com.raytheon.uf.edex.wmo.message.AFOSProductId;
import com.raytheon.uf.edex.wmo.message.WMOHeader;
@ -96,8 +95,6 @@ public abstract class WMOMessageSeparator extends AbstractRecordSeparator {
protected final String traceId;
protected final TextDBStaticData staticData;
protected final String siteId;
protected final TextDecoderMode mode;
@ -133,8 +130,6 @@ public abstract class WMOMessageSeparator extends AbstractRecordSeparator {
this.siteId = siteId;
this.wmoHeader = wmoHeader;
this.mode = mode;
staticData = TextDBStaticData.instance(siteId);
// localCCC = staticData.getProductId("LOCALCCC ").substring(0, 3);
}
/**
@ -282,7 +277,6 @@ public abstract class WMOMessageSeparator extends AbstractRecordSeparator {
return numSkipped;
}
/*
* private AFOSProductId createProductId() { AFOSProductId productId = null;
*
@ -382,10 +376,11 @@ public abstract class WMOMessageSeparator extends AbstractRecordSeparator {
//
// ---------------------------------------------------------------------------
static boolean checkCharNum(char x) {
if ((x > 64) && (x < 91))
if ((x > 64) && (x < 91)) {
return true;
else if ((x > 47) && (x < 58))
} else if ((x > 47) && (x < 58)) {
return true;
}
return false;
}

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>

View file

@ -1,7 +1,11 @@
#Thu Mar 26 10:27:35 CDT 2009
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.compiler.source=1.7

View file

@ -18,7 +18,7 @@ Export-Package: com.raytheon.edex.textdb.alarms,
com.raytheon.edex.textdb.dao,
com.raytheon.edex.textdb.dbapi.impl,
com.raytheon.edex.textdb.fax
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Import-Package: com.raytheon.uf.common.message,
com.raytheon.uf.common.serialization.comm,
org.apache.commons.logging

View file

@ -21,31 +21,42 @@ package com.raytheon.edex.textdb.dbapi.impl;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import com.raytheon.uf.common.localization.IPathManager;
import com.raytheon.uf.common.localization.LocalizationContext;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType;
import com.raytheon.uf.common.localization.LocalizationFile;
import com.raytheon.uf.common.localization.PathManagerFactory;
import com.raytheon.uf.common.site.SiteMap;
import com.raytheon.uf.common.status.IUFStatusHandler;
import com.raytheon.uf.common.status.UFStatus;
import com.raytheon.uf.common.util.CollectionUtil;
import com.raytheon.uf.common.util.FileUtil;
import com.raytheon.uf.common.util.StringUtil;
/**
* TODO Add Description
* A singleton class that maintains static data used by the TextDB (e.g., the
* mappings needed to perform lookups for AFOS and WMO IDs). Data is lazily
* loaded by data file.
*
* <pre>
* SOFTWARE HISTORY
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Sep 2, 2008 1538 jkorman Initial creation
* Sep 02, 2008 1538 jkorman Initial creation
* Jul 10, 2009 2191 rjpeter Added additional methods.
* Apr 01, 2014 2915 dgilling Major re-factor, all methods are now
* static.
* </pre>
*
* @author jkorman
@ -54,6 +65,9 @@ import com.raytheon.uf.common.site.SiteMap;
public class TextDBStaticData {
private static final IUFStatusHandler statusHandler = UFStatus
.getHandler(TextDBStaticData.class);
private static final int COLLECTIVE_TABLE_KEY_LEN = 6;
private static final int UPAIR_KEY_LEN = 6;
@ -62,66 +76,85 @@ public class TextDBStaticData {
private static final int BIT_TABLE_KEY_LEN = 3;
private static final int ISPAN_KEY_LEN = 10;
private static final int STD_SEPARATOR_LEN = 1;
private static final int ISPAN_SEPARATOR_LEN = 2;
private static boolean exclusionFileInplay = System
.getenv("STD_TEXT_EXCLUSION") != null;
private static Map<String, TextDBStaticData> instanceMap = new HashMap<String, TextDBStaticData>();
private static final String TEXTDB = "textdb";
private boolean tablesLoaded = false;
private static final String COLLECTIVE_TABLE_PATH = FileUtil.join(TEXTDB,
"collective_table.dat");
private Map<String, String> stdCollectiveMap = new HashMap<String, String>();
private static final String UPAIR_TABLE_PATH = FileUtil.join(TEXTDB,
"upair_table.dat");
private Map<String, String> uaCollectiveMap = new HashMap<String, String>();
private static final String STATION_TABLE_PATH = FileUtil.join(TEXTDB,
"station_table.dat");
private Map<String, String> stationTable = new HashMap<String, String>();
private static final String EXCLUSION_PRODUCT_LIST_PATH = FileUtil.join(
TEXTDB, "exclusionProductList.dat");
private Map<String, String> nationalTable = new HashMap<String, String>();
private static final String CHECK_PRODUCT_FILE_PATH = FileUtil.join(TEXTDB,
"checkProductFile.dat");
private Map<String, String> ispanTable = new HashMap<String, String>();
private static final String ISPAN_TABLE_PATH = FileUtil.join(TEXTDB,
"ispan_table.dat");
private Map<String, String> bitTable = new HashMap<String, String>();
private static final String BIT_TABLE_PATH = FileUtil.join(TEXTDB,
"bit_table.dat");
private Set<String> exclusionList = new HashSet<String>();
private static final AtomicBoolean stdCollectiveLoaded = new AtomicBoolean(
false);
private Set<String> duplicateCheckList = new HashSet<String>();
private static final AtomicBoolean uaCollectiveLoaded = new AtomicBoolean(
false);
private String siteId = null;
private static final AtomicBoolean stationTableLoaded = new AtomicBoolean(
false);
/**
*
*/
private TextDBStaticData(String siteId) {
this.siteId = siteId;
populateTables();
private static final AtomicBoolean ispanTableLoaded = new AtomicBoolean(
false);
private static final AtomicBoolean bitTableLoaded = new AtomicBoolean(false);
private static final AtomicBoolean exclusionListLoaded = new AtomicBoolean(
false);
private static final AtomicBoolean dupCheckListLoaded = new AtomicBoolean(
false);
private static final Map<String, String> stdCollectiveMap = new HashMap<>();
private static final Map<String, String> uaCollectiveMap = new HashMap<>();
private static final Map<String, String> stationTable = new HashMap<>();
private static final Map<String, String> ispanTable = new HashMap<>();
private static final Map<String, String> bitTable = new HashMap<>();
private static final Collection<String> exclusionList = new HashSet<>();
private static final Collection<String> duplicateCheckList = new HashSet<>();
private TextDBStaticData() {
// prevent default constructor from being created.
throw new AssertionError();
}
public boolean areTablesLoaded() {
return tablesLoaded;
}
/**
*
* @return
*/
public synchronized static TextDBStaticData instance(String siteId) {
TextDBStaticData instance = instanceMap.get(siteId);
if (instance == null) {
instance = new TextDBStaticData(siteId);
instanceMap.put(siteId, instance);
}
return instance;
}
public synchronized static void setDirty() {
for (String key : instanceMap.keySet()) {
if (null != instanceMap.get(key)) {
instanceMap.get(key).makeDirty();
}
}
}
public synchronized void makeDirty() {
tablesLoaded = false;
public static void setDirty() {
stdCollectiveLoaded.set(false);
uaCollectiveLoaded.set(false);
stationTableLoaded.set(false);
ispanTableLoaded.set(false);
bitTableLoaded.set(false);
exclusionListLoaded.set(false);
dupCheckListLoaded.set(false);
}
/**
@ -129,12 +162,15 @@ public class TextDBStaticData {
* @param dataDes
* @return
*/
public synchronized String matchStdCollective(String dataDes) {
if (!tablesLoaded) {
reload();
public static String matchStdCollective(String dataDes) {
if (!stdCollectiveLoaded.get()) {
populateMap(stdCollectiveMap, stdCollectiveLoaded,
COLLECTIVE_TABLE_PATH, COLLECTIVE_TABLE_KEY_LEN,
STD_SEPARATOR_LEN);
}
String retValue = null;
if (stdCollectiveMap != null) {
synchronized (stdCollectiveMap) {
retValue = stdCollectiveMap.get(dataDes);
}
return retValue;
@ -145,13 +181,14 @@ public class TextDBStaticData {
* @param dataDes
* @return
*/
public synchronized String matchUACollective(String dataDes) {
if (!tablesLoaded) {
reload();
public static String matchUACollective(String dataDes) {
if (!uaCollectiveLoaded.get()) {
populateMap(uaCollectiveMap, uaCollectiveLoaded, UPAIR_TABLE_PATH,
UPAIR_KEY_LEN, STD_SEPARATOR_LEN);
}
String retValue = null;
if (uaCollectiveMap != null) {
synchronized (uaCollectiveMap) {
retValue = uaCollectiveMap.get(dataDes);
}
return retValue;
@ -163,12 +200,14 @@ public class TextDBStaticData {
* @param WMOId
* @return
*/
public synchronized String mapWMOToICAO(String WMOId) {
if (!tablesLoaded) {
reload();
public static String mapWMOToICAO(String WMOId) {
if (!stationTableLoaded.get()) {
populateMap(stationTable, stationTableLoaded, STATION_TABLE_PATH,
STATION_TABLE_KEY_LEN, STD_SEPARATOR_LEN);
}
String retValue = null;
if (stationTable != null) {
synchronized (stationTable) {
retValue = stationTable.get(WMOId);
}
return retValue;
@ -180,7 +219,7 @@ public class TextDBStaticData {
* @param WMOId
* @return
*/
public synchronized String mapICAOToCCC(String icao) {
public static String mapICAOToCCC(String icao) {
return SiteMap.getInstance().mapICAOToCCC(icao);
}
@ -191,7 +230,7 @@ public class TextDBStaticData {
* @param WMOId
* @return
*/
public synchronized String mapWMOToCCC(String icao) {
public static String mapWMOToCCC(String icao) {
return mapICAOToCCC(mapWMOToICAO(icao));
}
@ -200,11 +239,17 @@ public class TextDBStaticData {
* @param ispanId
* @return
*/
public synchronized boolean isMappedISpanId(String ispanId) {
if (!tablesLoaded) {
reload();
public static boolean isMappedISpanId(String ispanId) {
if (!ispanTableLoaded.get()) {
populateMap(ispanTable, ispanTableLoaded, ISPAN_TABLE_PATH,
ISPAN_KEY_LEN, ISPAN_SEPARATOR_LEN);
}
return ispanTable.containsKey(ispanId);
boolean containsKey = false;
synchronized (ispanTable) {
containsKey = ispanTable.containsKey(ispanId);
}
return containsKey;
}
/**
@ -212,11 +257,21 @@ public class TextDBStaticData {
* @param afosId
* @return
*/
public synchronized boolean isExcluded(String afosId) {
if (!tablesLoaded) {
reload();
public static boolean isExcluded(String afosId) {
if (!exclusionListLoaded.get()) {
if (exclusionFileInplay) {
populateCollection(exclusionList, exclusionListLoaded,
EXCLUSION_PRODUCT_LIST_PATH);
} else {
synchronized (exclusionList) {
exclusionList.clear();
exclusionListLoaded.set(true);
}
if (exclusionList != null && exclusionList.size() > 0) {
}
}
synchronized (exclusionList) {
if (!CollectionUtil.isNullOrEmpty(exclusionList)) {
// If a product ID in the exclusion file list that matches the
// incoming product's ID is found, end the process; otherwise,
// continue searching through the end of the list.
@ -263,6 +318,7 @@ public class TextDBStaticData {
// search full product id
return exclusionList.contains(afosId);
}
}
return false;
}
@ -272,12 +328,18 @@ public class TextDBStaticData {
* @param afosId
* @return
*/
public synchronized boolean checkForDuplicate(String afosId) {
boolean retVal = false;
if (duplicateCheckList != null && duplicateCheckList.size() > 0) {
retVal = duplicateCheckList.contains(afosId);
public static boolean checkForDuplicate(String afosId) {
if (!dupCheckListLoaded.get()) {
populateCollection(duplicateCheckList, dupCheckListLoaded,
CHECK_PRODUCT_FILE_PATH);
}
boolean retVal = false;
synchronized (duplicateCheckList) {
if (!CollectionUtil.isNullOrEmpty(duplicateCheckList)) {
retVal = duplicateCheckList.contains(afosId);
}
}
return retVal;
}
@ -286,11 +348,17 @@ public class TextDBStaticData {
* @param ispanId
* @return
*/
public synchronized String getProductId(String ispanId) {
if (!tablesLoaded) {
reload();
public static String getProductId(String ispanId) {
if (!ispanTableLoaded.get()) {
populateMap(ispanTable, ispanTableLoaded, ISPAN_TABLE_PATH,
ISPAN_KEY_LEN, ISPAN_SEPARATOR_LEN);
}
return ispanTable.get(ispanId);
String retVal = null;
synchronized (ispanTable) {
retVal = ispanTable.get(ispanId);
}
return retVal;
}
/**
@ -298,11 +366,19 @@ public class TextDBStaticData {
* @param nnnId
* @return
*/
public synchronized String getSiteIdFromNNN(String nnnId) {
if (!tablesLoaded) {
reload();
public static String getSiteIdFromNNN(String nnnId, String siteId) {
if (!bitTableLoaded.get()) {
populateMap(bitTable, bitTableLoaded, BIT_TABLE_PATH,
BIT_TABLE_KEY_LEN, STD_SEPARATOR_LEN);
}
return bitTable.get(nnnId);
String value = null;
synchronized (bitTable) {
value = bitTable.get(nnnId);
}
String retVal = ("AAA".equals(value)) ? SiteMap.getInstance()
.getCCCFromXXXCode(siteId) : value;
return retVal;
}
/**
@ -310,7 +386,7 @@ public class TextDBStaticData {
* @param wmoCccc
* @return
*/
public synchronized String getAfosCCC(String wmoCccc) {
public static String getAfosCCC(String wmoCccc) {
return SiteMap.getInstance().getCCCFromXXXCode(wmoCccc);
}
@ -319,85 +395,48 @@ public class TextDBStaticData {
* @param v
* @return
*/
public synchronized String getAFOSTableMap(String v) {
public static String getAFOSTableMap(String v) {
return SiteMap.getInstance().getAFOSTableMap(v);
}
/**
* Cause the internal tables to be reloaded. <code>
* TextDBStaticData.instance(site).reload();
* </code>
*/
public void reload() {
synchronized (this) {
// Drop the old maps.
stdCollectiveMap = null;
uaCollectiveMap = null;
stationTable = null;
nationalTable = null;
ispanTable = null;
bitTable = null;
exclusionList = null;
duplicateCheckList = null;
// and repopulate.
populateTables();
private static void populateMap(final Map<String, String> dataMap,
final AtomicBoolean loadedFlag, final String filePath,
final int keyLen, final int separatorLen) {
synchronized (dataMap) {
if (loadedFlag.get()) {
return;
}
try {
Map<String, String> newData = loadFileToMap(filePath, keyLen,
separatorLen);
dataMap.clear();
dataMap.putAll(newData);
loadedFlag.set(true);
} catch (IOException e) {
statusHandler.error(
"Could not load TextDBStaticData from file ["
+ filePath + "]", e);
}
}
}
/**
* Populate the internal data maps.
*/
private void populateTables() {
tablesLoaded = true;
private static void populateCollection(final Collection<String> dataList,
final AtomicBoolean loadedFlag, final String filePath) {
synchronized (dataList) {
if (loadedFlag.get()) {
return;
}
// ******************************
if (stdCollectiveMap != null) {
stdCollectiveMap = new HashMap<String, String>();
}
tablesLoaded &= loadFile("textdb/collective_table.dat",
stdCollectiveMap, COLLECTIVE_TABLE_KEY_LEN);
// ******************************
if (uaCollectiveMap != null) {
uaCollectiveMap = new HashMap<String, String>();
}
tablesLoaded &= loadFile("textdb/upair_table.dat", uaCollectiveMap,
UPAIR_KEY_LEN);
// ******************************
if (stationTable != null) {
stationTable = new HashMap<String, String>();
}
tablesLoaded &= loadFile("textdb/station_table.dat", stationTable,
STATION_TABLE_KEY_LEN);
// ******************************
if (exclusionList != null) {
exclusionList = new HashSet<String>();
}
if (exclusionFileInplay) {
tablesLoaded &= loadFile("textdb/exclusionProductList.dat",
exclusionList);
}
// ******************************
if (duplicateCheckList != null) {
duplicateCheckList = new HashSet<String>();
}
tablesLoaded &= loadFile("textdb/checkProductFile.dat",
duplicateCheckList);
// ******************************
if (ispanTable != null) {
ispanTable = new HashMap<String, String>();
}
tablesLoaded &= loadISpanFile("textdb/ispan_table.dat", ispanTable);
// ******************************
if (bitTable != null) {
bitTable = new HashMap<String, String>();
}
tablesLoaded &= loadFile("textdb/bit_table.dat", bitTable,
BIT_TABLE_KEY_LEN);
String node = SiteMap.getInstance().getCCCFromXXXCode(siteId);
for (String s : bitTable.keySet()) {
if ("AAA".equals(bitTable.get(s))) {
bitTable.put(s, node);
try {
Set<String> newData = loadFileToSet(filePath);
dataList.clear();
dataList.addAll(newData);
loadedFlag.set(true);
} catch (IOException e) {
statusHandler.error(
"Could not load TextDBStaticData from file ["
+ filePath + "]", e);
}
}
}
@ -405,163 +444,126 @@ public class TextDBStaticData {
/**
* Read in the desired static file, parse its contains and place results in
* the map. This assumes that each line of the file is in the following
* format: <li>(KEY)(DEL_CHAR)(VALUE)</li> <li>(KEY) is a string of keyLen
* characters that may include spaces</li> <li>(DEL_CHAR) a single character
* delimiter normally a space or equal sign</li> <li>(VALUE) the reset of
* the line that is mapped to (KEY)</li>
* format: (KEY)(DELIMETER)(VALUE)
* <ul>
* <li>(KEY) is a string of <code>keyLen</code> characters that may include
* spaces.</li>
* <li>(DELIMETER) is a string of <code>separatorLen</code> chars (usually,
* a space or equal sign or multiple spaces).</li>
* <li>(VALUE) the reset of the line that is mapped to (KEY).</li>
* </ul>
*
* @param filename
* - file to parse
* @param map
* - parse results
* File to parse
* @param keyLen
* - Length of the file's keys
* @return - true when file read and parsed otherwise false
* Length of the file's keys
* @param separatorLen
* Length of the separator between keys and values.
* @return A <code>Map</code> pairing the keys to their values.
* @throws IOException
* If any errors occur reading the specified input file.
* @throws FileNotFoundException
* If the specified file cannot be located in the localization
* hierarchy or if the file cannot be opened for reading.
*/
private boolean loadFile(String filename, Map<String, String> map,
final int keyLen) {
boolean loaded = false;
InputStream strm = null;
BufferedReader bf = null;
IPathManager pathMgr = PathManagerFactory.getPathManager();
LocalizationContext lc = pathMgr.getContext(
LocalizationType.COMMON_STATIC, LocalizationLevel.BASE);
File file = pathMgr.getFile(lc, filename);
HashMap<String, String> dataMap = new HashMap<String, String>();
try {
try {
strm = new FileInputStream(file);
if (strm != null) {
bf = new BufferedReader(new InputStreamReader(strm));
private static Map<String, String> loadFileToMap(final String filename,
final int keyLen, final int separatorLen)
throws FileNotFoundException, IOException {
File file = locateFile(filename);
if (file == null) {
throw new FileNotFoundException("Could not locate file ["
+ filename + "] in localization hierarchy.");
}
Map<String, String> retVal = new HashMap<>();
try (BufferedReader inFile = new BufferedReader(new FileReader(file))) {
String line = null;
while ((line = bf.readLine()) != null) {
while ((line = inFile.readLine()) != null) {
if ((line.length() < (keyLen + separatorLen + 1))
|| (line.startsWith("#"))) {
continue;
}
/*
* A note on why we're performing this selective trimming: The
* format of the current NDM files on the web are formatted
* slightly differently than those in the current A2 baseline.
*
* The A2 baseline files have a single separator char between
* key and value, and no excess whitespace between the separator
* char and the value. The new updated files on the web,
* however, vary between a single separator char and using 2
* spaces as a separator.
*
* To allow both formats to work we expect a fixed-length key,
* and left-trim the value string in case we get an extra
* leading space char from the newer format files.
*/
String dataKey = line.substring(0, keyLen);
String tblData = line.substring(keyLen + 1);
String tData = dataMap.get(tblData);
if (tData == null) {
dataMap.put(tblData, tblData);
tData = tblData;
}
map.put(dataKey, tData);
}
loaded = true;
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
} finally {
if (bf != null) {
try {
bf.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
if (dataMap != null) {
dataMap.clear();
String tblData = StringUtil.ltrim(line.substring(keyLen
+ separatorLen));
retVal.put(dataKey, tblData);
}
}
return loaded;
return retVal;
}
/**
* Read in the desired static file, parse its contains and place results in
* a <code>Set</code>.
*
* @param filename
* @param list
* @return
* The path to the input file in the localization hierarchy.
* @return A <code>Set</code> containing all the valid values in the input
* file.
* @throws FileNotFoundException
* If the specified file cannot be located in the localization
* hierarchy or if the file cannot be opened for reading.
* @throws IOException
* If any errors occur reading the specified input file.
*/
private boolean loadFile(String filename, Set<String> list) {
boolean loaded = false;
InputStream strm = null;
BufferedReader bf = null;
IPathManager pathMgr = PathManagerFactory.getPathManager();
LocalizationContext lc = pathMgr.getContext(
LocalizationType.COMMON_STATIC, LocalizationLevel.BASE);
File file = pathMgr.getFile(lc, filename);
try {
try {
strm = new FileInputStream(file);
if (strm != null) {
bf = new BufferedReader(new InputStreamReader(strm));
private static Set<String> loadFileToSet(final String filename)
throws FileNotFoundException, IOException {
File file = locateFile(filename);
if (file == null) {
throw new FileNotFoundException("Could not locate file ["
+ filename + "] in localization hierarchy.");
}
Set<String> retVal = new HashSet<>();
try (BufferedReader inFile = new BufferedReader(new FileReader(file))) {
String line = null;
while ((line = bf.readLine()) != null) {
while ((line = inFile.readLine()) != null) {
line = line.trim();
if (line.length() > 0 && !line.startsWith("#")
if (!line.isEmpty() && !line.startsWith("#")
&& !"000000000".equals(line)) {
list.add(line);
}
}
loaded = true;
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
} finally {
if (bf != null) {
try {
bf.close();
} catch (IOException ioe) {
ioe.printStackTrace();
retVal.add(line);
}
}
}
return loaded;
return retVal;
}
/**
*
* @param filename
* @param map
* @return
*/
private boolean loadISpanFile(String filename, Map<String, String> map) {
boolean loaded = false;
InputStream strm = null;
BufferedReader bf = null;
private static File locateFile(final String filename) {
IPathManager pathMgr = PathManagerFactory.getPathManager();
LocalizationContext lc = pathMgr.getContext(
LocalizationType.COMMON_STATIC, LocalizationLevel.BASE);
File file = pathMgr.getFile(lc, filename);
Map<LocalizationLevel, LocalizationFile> tieredFile = pathMgr
.getTieredLocalizationFile(LocalizationType.COMMON_STATIC,
filename);
LocalizationContext[] contexts = pathMgr
.getLocalSearchHierarchy(LocalizationType.COMMON_STATIC);
try {
try {
strm = new FileInputStream(file);
if (strm != null) {
bf = new BufferedReader(new InputStreamReader(strm));
String line = null;
while ((line = bf.readLine()) != null) {
map.put(line.substring(0, 10), line.substring(12));
}
loaded = true;
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
} finally {
if (bf != null) {
try {
bf.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
File file = null;
for (LocalizationContext context : contexts) {
LocalizationLevel level = context.getLocalizationLevel();
LocalizationFile lFile = tieredFile.get(level);
if (lFile != null) {
file = lFile.getFile();
break;
}
}
return loaded;
return file;
}
}

View file

@ -19,15 +19,8 @@
**/
package com.raytheon.edex.textdb.ingest;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import com.raytheon.edex.textdb.dbapi.impl.TextDBStaticData;
import com.raytheon.uf.common.localization.IPathManager;
@ -37,7 +30,7 @@ import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType;
import com.raytheon.uf.common.localization.PathManagerFactory;
import com.raytheon.uf.common.status.IUFStatusHandler;
import com.raytheon.uf.common.status.UFStatus;
import com.raytheon.uf.common.status.UFStatus.Priority;
import com.raytheon.uf.common.util.FileUtil;
import com.raytheon.uf.edex.ndm.ingest.INationalDatasetSubscriber;
/**
@ -52,6 +45,7 @@ import com.raytheon.uf.edex.ndm.ingest.INationalDatasetSubscriber;
* Jan 25, 2011 bfarmer Initial creation
* Oct 18, 2011 10909 rferrel notify() now saves a file.
* Mar 06, 2014 2876 mpduff New NDM plugin.
* Mar 20, 2014 2915 dgilling Code cleanup.
*
* </pre>
*
@ -69,50 +63,16 @@ public class TextDBStaticDataSubscriber implements INationalDatasetSubscriber {
// in the BASE directory.
IPathManager pathMgr = PathManagerFactory.getPathManager();
LocalizationContext lc = pathMgr.getContext(
LocalizationType.COMMON_STATIC, LocalizationLevel.BASE);
File outFile = pathMgr.getFile(lc, "textdb/" + fileName);
saveFile(file, outFile);
}
LocalizationType.COMMON_STATIC, LocalizationLevel.CONFIGURED);
File outFile = pathMgr.getFile(lc, FileUtil.join("textdb", fileName));
private void saveFile(File file, File outFile) {
if ((file != null) && file.exists()) {
BufferedReader fis = null;
BufferedWriter fos = null;
try {
fis = new BufferedReader(new InputStreamReader(
new FileInputStream(file)));
fos = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(outFile)));
String line = null;
try {
while ((line = fis.readLine()) != null) {
fos.write(line);
fos.newLine();
}
} catch (IOException e) {
statusHandler.handle(Priority.PROBLEM,
"Could not read file: " + file.getName(), e);
}
} catch (FileNotFoundException e) {
statusHandler.handle(Priority.PROBLEM, "Failed to find file: "
+ file.getName(), e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// ignore
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
// ignore
}
}
}
}
FileUtil.copyFile(file, outFile);
TextDBStaticData.setDirty();
} catch (IOException e) {
statusHandler.error("Unable to copy textdb static data file ["
+ file.getPath() + "] to destination [" + outFile.getPath()
+ "].", e);
}
}
}

View file

@ -41,6 +41,7 @@ import org.apache.commons.lang.StringUtils;
* Jul 13, 2012 740 djohnson Add join.
* Nov 09, 2012 1322 djohnson Add NEWLINE, createMessage.
* Mar 02, 2013 1970 bgonzale Added fast string replacement method.
* Apr 02, 2014 2915 dgilling Added left and right trim methods.
*
* </pre>
*
@ -323,4 +324,42 @@ public final class StringUtil {
}
}
/**
* Returns a copy of the string, with only leading whitespace omitted.
* <p>
* Like String.trim(), whitespace is defined as any character with a code
* less than or equal to <code>'&#92;u0020'</code> (the space character).
*
* @param s
* The <code>String</code> to trim.
* @return A copy of this string with leading white space removed, or the
* passed in string if it has no leading white space.
*/
public static String ltrim(String s) {
int i = 0;
while ((i < s.length()) && (s.charAt(i) <= ' ')) {
i++;
}
return (i > 0) ? s.substring(i) : s;
}
/**
* Returns a copy of the string, with only trailing whitespace omitted.
* <p>
* Like String.trim(), whitespace is defined as any character with a code
* less than or equal to <code>'&#92;u0020'</code> (the space character).
*
* @param s
* The <code>String</code> to trim.
* @return A copy of this string with trailing white space removed, or the
* passed in string if it has no trailing white space.
*/
public static String rtrim(String s) {
int i = s.length();
while ((i > 0) && (s.charAt(i - 1) <= ' ')) {
i--;
}
return (i < s.length()) ? s.substring(0, i) : s;
}
}