//
// ReadWxText.java
//
/*
This source file is part of the edu.wisc.ssec.mcidas package and is
Copyright (C) 1998 - 2017 by Tom Whittaker, Tommy Jasmin, Tom Rink,
Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
and others.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/
package edu.wisc.ssec.mcidas.adde;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.lang.*;
import java.util.*;
import edu.wisc.ssec.mcidas.McIDASUtil;
/**
* Read text from an ADDE server interface for McIDAS ADDE data sets.
* This class handles file, weather text and obs text requests.
* <P>
* <pre>
* For File Reading:
* URLs must all have the following format
* adde://host/text?file=filename.ext
*
* there can be any valid combination of the following supported keywords:
*
* file - name of text file on ADDE server
*
* the following keywords are required:
*
* file
*
* an example URL might look like:
* adde://viper/text?file=filename.ext
*
* For Weather Text Reading:
* URLs must all have the following format
* adde://host/wxtext?group=group&key1=value1&key2=val2....&keyn=valn
*
* there can be any valid combination of the following supported keywords:
*
* group=<group> weather text group (default= RTWXTEXT)
* prod=<product> predefind product name
* apro=<val1 .. valn> AFOS/AWIPS product headers to match (don't
* use with wmo keyword
* astn=<val1 .. valn> AFOS/AWIPS stations to match
* wmo= <val1 .. valn> WMO product headers to match (don't
* use with apro keyword
* wstn=<val1 .. valn> WMO stations to match
* day=<start end> range of days to search
* dtime=<numhours> maximum number of hours to search back (def=96)
* match=<match strings> list of character match strings to find from text
* num=<num> number of matches to find (def=1)
*
* the following keywords are required:
*
* day (bug causes it not to default to current day)
* one of the selection criteria
*
* an example URL might look like:
* adde://adde.ucar.edu/wxtext?group=rtwxtext&prod=zone_fcst&astn=bou
*
* For Observational Text Reading:
* URLs must all have the following format
* adde://host/obtext?group=group&descr=descr&key1=value1....&keyn=valn
*
* there can be any valid combination of the following supported keywords:
*
* group=<group> weather text group (default= RTWXTEXT)
* descr=<descriptor> weather text subgroup (default=SFCHOURLY)
* id=<id1 id2 ... idn> list of station ids
* co=<co1 co2 ... con> list of countries
* reg=<reg1 reg2..regn> list of regions
* newest=<day hour> most recent time to allow in request
* (def=current time)
* oldest=<day hour> oldest observation time to allow in request
* type=<type> numeric value for the type of ob
* nhours=<numhours> maximum number of hours to search
* num=<num> number of matches to find (def=1)
*
* the following keywords are required:
*
* group
* descr
* id, co, or reg
*
* an example URL might look like:
* adde://adde.ucar.edu/obtext?group=rtwxtext&descr=sfchourly&id=kden&num=2
*
* </pre>
*
* @author Tom Whittaker/Don Murray
*
*/
public class AddeTextReader {
// load protocol for ADDE URLs
// See java.net.URL for explanation of URL handling
static
{
try
{
String handlers = System.getProperty("java.protocol.handler.pkgs");
String newProperty = null;
if (handlers == null)
newProperty = "edu.wisc.ssec.mcidas";
else if (handlers.indexOf("edu.wisc.ssec.mcidas") < 0)
newProperty = "edu.wisc.ssec.mcidas | " + handlers;
if (newProperty != null) // was set above
System.setProperty("java.protocol.handler.pkgs", newProperty);
}
catch (Exception e)
{
System.out.println(
"Unable to set System Property: java.protocol.handler.pkgs");
}
}
private int status=0; // read status
private String statusString = "OK";
private boolean debug = false; // debug
private Vector linesOfText = null;
private URLConnection urlc;
private DataInputStream dis;
private final int HEARTBEAT = 11223344;
private List<WxTextProduct> wxTextProds = new ArrayList<WxTextProduct>();
/**
* Creates an AddeTextReader object that allows reading an ADDE
* text file or weather text
*
* @param request ADDE URL to read from. See class javadoc.
*/
public AddeTextReader(String request) {
try {
URL url = new URL(request);
debug = request.indexOf("debug=true") > 0;
if (debug) System.out.println("Request: "+request);
urlc = url.openConnection();
InputStream is = urlc.getInputStream();
dis = new DataInputStream(is);
}
catch (AddeURLException ae)
{
status = -1;
statusString = "No data found";
String aes = ae.toString();
if (aes.indexOf(" Accounting ") != -1) {
statusString = "No accounting data";
status = -3;
}
if (debug) System.out.println("AddeTextReader Exception:"+aes);
}
catch (Exception e)
{
status = -2;
if (debug) System.out.println("AddeTextReader Exception:"+e);
statusString = "Error opening connection: "+e;
}
linesOfText = new Vector();
if (status == 0) readText(((AddeURLConnection) urlc).getRequestType());
if (linesOfText.size() < 1) statusString = "No data read";
status = linesOfText.size();
}
private void readText(int reqType) {
switch(reqType) {
case AddeURLConnection.TXTG:
readTextFile();
break;
case AddeURLConnection.WTXG:
readWxText();
break;
case AddeURLConnection.OBTG:
readObText();
break;
}
}
private void readTextFile() {
int numBytes;
try {
numBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
if (debug)
System.out.println("ReadTextFile: initial numBytes = " + numBytes);
numBytes = dis.readInt();
while ((numBytes = dis.readInt()) != 0) {
if (debug)
System.out.println("ReadTextFile: numBytes = " + numBytes);
byte[] data = new byte[numBytes];
dis.readFully(data,0,numBytes);
String s = new String(data);
if (debug) System.out.println(s);
linesOfText.addElement(s);
}
} catch (Exception iox) {
statusString = " "+iox;
}
}
private void readWxText() {
int numBytes;
/*
From the McIDAS programmer's ref:
The server sends the client the following information:
o 4-byte value containing the length of the client request
string; this lets users know how their request was expanded
when the PROD keyword is specified the expanded client
request string
o heartbeat value if needed
o 4-byte value containing the total number of bytes of
data for this text block, including the 64-byte header and
the text 64-byte text header
o n bytes of 80-character text, blank padded
The last three pieces of information are repeated until no more
data is found.
*/
try {
numBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
if (debug)
System.out.println("ReadWxText: initial numBytes = " + numBytes);
// Read in the expanded client request string
byte[] expandedRequest = new byte[numBytes];
dis.readFully(expandedRequest,0,numBytes);
String s = new String(expandedRequest);
if (debug)
System.out.println("Server interpreted request as:\n " + s);
// look for heartbeat
while ((numBytes = dis.readInt()) == 4) {
int check = dis.readInt();
if (check != HEARTBEAT) { // must be number of bytes to read
numBytes = check;
break;
}
}
// Now we go read the data
if (debug) System.out.println("numBytes for text = "+numBytes);
wxTextProds = new ArrayList<WxTextProduct>();
while (numBytes != 0) {
// read in the header
byte[] header = new byte[64];
dis.readFully(header,0,64);
WxTextProduct wtp = new WxTextProduct(header);
String head = new String(header);
// note this is not true text so prints as garbage
//if (debug) System.out.println(decodeWxTextHeader(header));
if (debug) System.out.println(wtp);
// read in the text in 80 byte chunks
byte[] text = new byte[numBytes-64];
dis.readFully(text,0,numBytes-64);
int nLines = text.length/80;
if (debug) System.out.println("nLines = " + nLines);
StringBuilder wxText = new StringBuilder();
for (int i = 0; i < nLines; i++)
{
String line = new String(text, i*80, 80);
linesOfText.add(line);
wxText.append(line);
wxText.append("\n");
}
wtp.setText(wxText.toString());
wxTextProds.add(wtp);
// read in next length, but check for heartbeat
while ((numBytes = dis.readInt()) == 4) {
int check = dis.readInt();
if (check != HEARTBEAT) { // must be number of bytes to read
numBytes = check;
break;
}
}
}
} catch (Exception iox) {
statusString = " "+iox;
}
}
private void readObText() {
int numBytes;
/*
From the McIDAS programmer's ref:
Once the data is formatted, the text data can be sent
to the client. The server sends the client the following
information:
o 4-byte value containing the length of the text header,
which is currently 96; when all the data is sent, this
value is reset to zero
o 96-byte text header
o 4-byte value containing the number of bytes of text to be sent
o n bytes of text; 80 characters per line, blank padded
This information should be repeated until no more data is found.
*/
try {
numBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
if (debug)
System.out.println("ReadObText: initial numBytes = " + numBytes);
while( numBytes != 0) {
// Read in the header
byte[] header = new byte[numBytes];
dis.readFully(header,0,numBytes);
String s = new String(header);
if (debug) System.out.println(decodeObsHeader(header));
numBytes = dis.readInt();
// Now we go read the data
if (debug) System.out.println("numBytes for text = "+numBytes);
// read in the data
byte[] text = new byte[numBytes];
dis.readFully(text,0,numBytes);
int nLines = text.length/80;
if (debug) System.out.println("nLines = " + nLines);
for (int i = 0; i < nLines; i++)
{
String line = new String(text, i*80, 80);
linesOfText.add(line);
}
numBytes = dis.readInt();
}
} catch (Exception iox) {
statusString = " "+iox;
}
}
/**
* Get a string representation of the status code
* @return human readable status
*/
public String getStatus() {
return statusString;
}
/**
* Return the status code of the read
* @return status code (>0 == read okay);
*/
public int getStatusCode() {
return status;
}
/**
* Return the number of lines of text that were read.
* @return number of lines.
*/
public int getNumLines() {
return linesOfText.size();
}
/**
* Return the text read from the server. If there was a problem
* the error message is returned.
* @return text from server or error message
*/
public String getText() {
StringBuffer buf = new StringBuffer();
if (getStatusCode() <= 0) {
buf.append(getStatus());
} else {
for (Iterator iter = linesOfText.iterator(); iter.hasNext();) {
buf.append((String) iter.next());
buf.append("\n");
}
}
return buf.toString();
}
public Vector getLinesOfText() {
Vector v = new Vector();
if (getStatusCode() <= 0) {
v.add(getStatus());
} else {
v.addAll(linesOfText);
}
return v;
}
public List<WxTextProduct> getWxTextProducts() {
List<WxTextProduct> retList = new ArrayList<WxTextProduct>();
retList.addAll(wxTextProds);
return retList;
}
/** test by running 'java edu.wisc.ssec.mcidas.adde.AddeTextReader' */
public static void main (String[] args)
throws Exception
{
String request = (args.length == 0)
? "adde://adde.ucar.edu/text?file=PUBLIC.SRV"
: args[0];
AddeTextReader atr = new AddeTextReader(request);
String status = atr.getStatus();
System.out.println("\n" + atr.getText());
}
private String decodeObsHeader(byte[] header) {
StringBuffer buf = new StringBuffer();
int[] values = McIDASUtil.bytesToIntegerArray(header,0,13);
buf.append("Ver = ");
buf.append(values[0]);
buf.append(" ObType = ");
buf.append(values[1]);
buf.append(" ActFlag = ");
buf.append(values[2]);
buf.append(" IDType = ");
buf.append(values[9]);
buf.append("\nStarting at ");
buf.append(values[3]);
buf.append(" ");
buf.append(values[4]);
buf.append(" ");
buf.append(values[5]);
buf.append("\nEnding at ");
buf.append(values[6]);
buf.append(" ");
buf.append(values[7]);
buf.append(" ");
buf.append(values[8]);
return buf.toString();
}
private String decodeWxTextHeader(byte[] header) {
StringBuffer buf = new StringBuffer();
int[] values = McIDASUtil.bytesToIntegerArray(header,0,13);
buf.append("SOU nb location day time WMO WSTN APRO ASTN\n");
buf.append("--- ---- -------- ------- ------ ------- ---- ---- ----\n");
buf.append(McIDASUtil.intBitsToString(values[0]));
buf.append(values[1]);
buf.append(" ");
buf.append(values[2]);
buf.append(" ");
buf.append(values[10]);
buf.append(" ");
buf.append(values[3]);
buf.append(" ");
buf.append(McIDASUtil.intBitsToString(values[4]));
buf.append(values[5]);
buf.append(" ");
buf.append(McIDASUtil.intBitsToString(values[6]));
buf.append(" ");
buf.append(McIDASUtil.intBitsToString(values[7]));
buf.append(" ");
buf.append(McIDASUtil.intBitsToString(values[8]));
return buf.toString();
}
}