package org.autoplot.datasource;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.das2.datum.DatumRange;
import org.das2.datum.DatumRangeUtil;
import org.das2.util.LoggerManager;
/**
* Class for containing the elemental parts of a URI, and utility
* routines for working with URIs.
*
* We need a working definition of well-formed and colloquial URIs:
*
* = well-formed URIs =
* :?
* :[?]
* :
* * they are valid URIs: they contain no spaces, etc.
* == params ==
* ampersand-delimited (&) list of name=value pairs, or just value.
* vap+cdaweb:ds=ac_k0_epm&H_lo&timerange=2010-01
* = colloquial URIs =
* * these are Strings that can be converted into URIs.
* * spaces in file names are converted into %20.
* * spaces in parameter lists are converted into pluses.
* * pluses in parameter lists are converted into %2B.
* * note that if there are pluses but the URI is valid, then pluses may be left alone.
*
*
* This routine knows nothing about the data source that will interpret the
* URI, so this needs to be established.
*
* @author jbf
*/
public class URISplit {
private static final Logger logger= LoggerManager.getLogger( LogNames.APDSS_URI );
/**
* The following are suggestions for parameter names to encourage consistency between implementations.
* See http://autoplot.org/developer.URI_syntax
* For example, if your URI accepts a time range like "Nov 2011", then use the timerange=Nov+2011 in your URI. If you
* do, then for example DefaultTimeSeriesBrowseEditor will work for you.
*/
/**
* time range subset.
*/
public static final String PARAM_TIME_RANGE= "timerange";
public static final String PARAM_TIME_RESOLUTION="resolution";
/**
* subset of rank 2 data. For example, columns of excel workbook or ascii table.
* rank2=[3,5] or rank2=Bx-Bz
*/
public static final String PARAM_RANK2="rank2";
/**
* used for the number of records to read.
*/
public static final String PARAM_REC_COUNT="recCount";
/**
* first positional parameter, typically interpreted the same as PARAM_ID
*/
public static final String PARAM_ARG_0="arg_0";
/**
* typically the dataset id.
*/
public static final String PARAM_ID="id";
/**
* some datasources support periodic checks to see if data sources have updated, such as:
* AggregatingDataSource
* AbstractDataSources (most of those based on files)
*/
public static final String PARAM_FILE_POLL_UPDATES= "filePollUpdates";
/**
* make the URI canonical, with the vap+<ext>: prefix.
* This will also now sort the parameters, when this can be done.
* @param suri, such as "/tmp/x.cdf"
* @return "vap+cdf:file:///tmp/x.cdf"
*/
public static String makeCanonical(String suri) {
logger.log(Level.FINEST, "makeCanonical {0}", suri);
if ( suri==null ) return null;
URISplit split= URISplit.parse(suri);
if ( !DataSourceRegistry.getInstance().hasParamOrder(suri) ) {
Map paramsLoose= URISplit.parseParams(split.params);
LinkedHashMap params= new LinkedHashMap<>();
String arg_0= paramsLoose.remove("arg_0");
if ( arg_0!=null ) params.put( "arg_0", arg_0 );
List keys= new ArrayList<>( paramsLoose.keySet() );
Collections.sort(keys);
if ( keys.remove("start_time") ) keys.add("start_time"); //
if ( keys.remove("end_time" ) ) keys.add("end_time");
for ( String k: keys ) {
params.put( k, paramsLoose.get(k) );
}
split.params= URISplit.formatParams(params);
if ( split.params.length()==0 ) {
split.params=null;
}
}
suri= URISplit.format(split); // make canonical
if ( !suri.startsWith("vap+") && split.ext!=null && split.ext.length()>1 ) {
suri= "vap+"+split.ext.substring(1)+":"+suri;
}
logger.log(Level.FINEST, "makeCanonical results in {0}", suri);
return suri;
}
/**
* make the URI colloquial, e.g. removing "vap+cdf:" from "vap+cdf:file:///tmp/x.cdf"
* URIs that do not have a resource URI are left alone.
* @param suri a URI
* @return the URI, more colloquial and readable.
*/
public static String makeColloquial(String suri) {
logger.log(Level.FINEST, "makeColloquial {0}", suri);
if ( suri==null ) return null;
suri= suri.trim();
if ( suri.equals("") ) return "";
URISplit split= URISplit.parse(suri);
if ( split.vapScheme!=null ) {
if ( split.ext!=null && split.ext.length()>1 && split.vapScheme.equals("vap+"+split.ext.substring(1) ) ) {
split.vapScheme= null;
}
}
String result= URISplit.format(split);
if ( result.endsWith("file:///") && suri.endsWith(":") ) { // kludge around "file:/// that is added to "vap+cdaweb:"
logger.log(Level.FINEST, "makeColloquial results in {0}", suri);
return suri;
}
logger.log(Level.FINEST, "makeColloquial results in {0}", result);
return result;
}
/**
* ensure that the reference, which may be relative, absolute.
* NOTE this is only implemented for unix filenames. TODO: Windows.
* For example:
* - /tmp/,foo.dat → /home/t/foo.dat
*
- /tmp/,/home/jbf/foo.dat → /home/jbf/foo.dat
*
* @param path the absolute directory.
* @param suri the URI, which may be relative to path.
* @return the absolute path
*/
public static String makeAbsolute( String path, String suri ) {
int i= suri.indexOf(':');
if ( i==-1 ) { // it's a file.
boolean isAbsolute= suri.startsWith("/");
if ( !isAbsolute ) {
String pwd= path;
if ( pwd.endsWith("/.") ) pwd= pwd.substring(0,pwd.length()-2);
if ( !pwd.endsWith("/")) {
pwd= pwd + "/"; //TODO: Windows...
}
suri= pwd + suri;
}
}
return suri;
}
/**
* scheme for Autoplot, if provided. e.g. vap+cdf.
*/
public String vapScheme;
/**
* scheme for resource, e.g. "file" or "https"
*/
public String scheme;
/**
* the complete, modified surl. file:///home/jbf/mydata.qds
* this is the resource name, and doesn't contain the vapScheme.
*/
public String surl;
/**
* the resource that is handled by the DataSource. This may be null if surl doesn't form a valid uri.
*
*/
public URI resourceUri;
/**
* the resource uri up to the authority, e.g. http://autoplot.org
*/
public String authority;
/**
* the resource uri including the path part.
*/
public String path;
/**
* contains the resource string up to the query part.
*/
public String file;
/**
* the file/resource extention, like ".cdf" or ".dat".
*/
public String ext;
/**
* contains the parameters part, a ampersand-delimited set of parameters. For example, column=field2&rank2.
*/
public String params;
/**
* additional processes to be applied to the URI. For example, slice0(0) means slice the dataset at this point.
*/
public String filters;
/**
* position of the caret after modifications to the surl are made. This
* is with respect to surl, the URI for the datasource, without the "vap" scheme.
*/
public int resourceUriCarotPos;
/**
* position of the caret after modifications to the surl are made. This
* is with respect to formatted URI, which probably includes the explicit "vap:" scheme.
*/
public int formatCarotPos;
static List otherSchemes= Collections.emptyList();
/**
* allow parsing of script:, bookmarks:, pngwalk:, etc
* @param otherSchemes
*/
public static void setOtherSchemes( List otherSchemes ) {
URISplit.otherSchemes= otherSchemes;
}
/**
* add "file:/" to a resource string that appears to reference the local filesystem.
* return the parsed string, or null if the string doesn't appear to be from a file.
* @param surl
* @param caretPos
* @return null or the URISplit
*/
public static URISplit maybeAddFile(String surl, int caretPos) {
URISplit result = new URISplit();
if (surl.length() == 0) {
surl = "file:///";
caretPos = surl.length();
result.surl = surl;
result.vapScheme = null;
result.resourceUriCarotPos = caretPos;
result.formatCarotPos = caretPos;
}
String scheme; // identify a scheme, if any. This might be vap+foo:, or http:
int i0 = surl.indexOf(':');
if (i0 == -1) {
scheme = "";
} else if (i0 == 1) { // one letter scheme is assumed to be windows drive letter.
scheme = "";
} else {
if ( surl.substring(0,i0).contains("/") ) {
scheme = "";
} else {
scheme = surl.substring(0, i0);
}
}
if ( scheme.startsWith("vap") || otherSchemes.contains(scheme) ) {
String resourcePart = surl.substring(i0 + 1);
if ( !scheme.equals("vap") ) { // legacy URIs would often have informationless "vap:" prefix. We remove this now.
result.vapScheme = scheme;
}
if (scheme.equals("vap+internal")) { // leave the resourcePart alone. TODO: jdbc and other non-file URIs.
result.surl= resourcePart;
} else {
URISplit resourceSplit = maybeAddFile(resourcePart, caretPos - (i0 + 1)); //TODO: jdbc and vap+inline
if ( resourceSplit==null ) {
result.surl= resourcePart;
result.file= "";
result.formatCarotPos= caretPos;
} else {
result.surl = resourceSplit.surl;
result.formatCarotPos = (caretPos > i0) ? resourceSplit.resourceUriCarotPos + (i0 + 1) : caretPos;
result.resourceUriCarotPos = result.formatCarotPos - (scheme.length() + 1); // with respect to resource part.
}
}
} else {
result.surl = surl;
result.resourceUriCarotPos = caretPos;
}
if (scheme.equals("")) {
boolean isFile= true;
int iquery= surl.indexOf('?');
if ( iquery==-1 ) {
int ieq= surl.indexOf('=');
//kludge in support for "ripples(30,30")
int ch0= surl.length()>0 ? surl.charAt(0) : (char)0;
int ch1= surl.length()>1 ? surl.charAt(1) : (char)0;
boolean notSlashStart= ch0!='/' && ch0!='\\' && ch1!='/' && ch1!='\\' && ch1!=':';
if ( notSlashStart || ( ieq>-1 && !(surl.charAt(0)=='/') ) ) {
isFile= false;
}
}
if ( !isFile ) {
return null;
} else {
if ( surl.startsWith("~/" ) ) { // finally add this to unix.
surl= System.getProperty("user.home") + surl.substring(1);
result.resourceUriCarotPos += System.getProperty("user.home").length()-1;
}
result.surl = "file://";
result.scheme= "file";
result.resourceUriCarotPos += 7;
if ((surl.charAt(0) == '/')) {
result.surl += surl;
} else {
result.surl += ('/' + surl); // Windows c:
result.resourceUriCarotPos += 1;
}
int iq= result.surl.indexOf('?');
if ( iq==-1 ) iq= result.surl.length();
result.surl = result.surl.replaceAll("\\\\", "/"); //TODO: what if \ in query part?
int spaceCount= charCount( result.surl, ' ', 0, result.surl.length() );
result.surl = replaceAll( result.surl, " ", "%20", 0, iq );
result.formatCarotPos+= spaceCount*2; //account for inserted characters.
result.resourceUriCarotPos+= spaceCount*2;
}
}
return result;
}
private static int charCount( String src, char find, int start, int end ) {
int count=0;
for ( int i=start; i
* scheme, http
* authority, http://www.example.com
* path, the directory with http://www.example.com/data/
* file, the file, http://www.example.com/data/myfile.nc
* ext, the extenion, .nc
* params, myVariable or null
*
* @param suri the uri to be parsed
* @return the components.
*/
public static URISplit parse(String suri) {
return parse(suri, 0, true);
}
/**
* return the vap scheme in split.vapScheme or the one inferred by the
* extension. Returns an empty string (not "vap") if one cannot be inferred.
* e.g:
* /home/jbf/myfile.jyds --> vap+jyds
* vap+txt:/home/jbf/myfile.csv --> vap+txt
* This was introduced as part of the effort to get rid of extraneous "vap:"s
* that would be added to URIs.
*
* @param split
* @return the vap scheme or empty string.
*/
public static String implicitVapScheme( URISplit split ) {
if ( split.vapScheme!=null ) return split.vapScheme;
if ( split.ext!=null && split.ext.length()>1 ) return "vap+"+split.ext.substring(1);
return "";
}
/**
* convenient method to remove a parameter (or parameters) from the list of parameters
* @param surl any URI or web address
* @param parm the name to remove
* @return the URI with the parameter removed, and the question mark removed when no parameters remain.
*/
public static String removeParam( String surl, String ... parm ) {
URISplit split= URISplit.parse(surl);
Map params= URISplit.parseParams( split.params );
for ( String p: parm ) {
params.remove(p);
}
split.params= URISplit.formatParams(params);
if ( params.isEmpty() ) split.params=null;
if ( split.vapScheme!=null && !surl.startsWith(split.vapScheme) ) split.vapScheme=null;
return URISplit.format(split);
}
/**
* convenient method for adding or replacing a parameter to the URI.
* @param surl any URI or web address
* @param name the parameter name to add
* @param value the parameter value to add
* @return the uri with the question mark and parameter added.
*/
public static String putParam( String surl, String name, String value ) {
URISplit split= URISplit.parse(surl);
Map params= URISplit.parseParams( split.params );
params.put( name, value );
split.params= URISplit.formatParams(params);
if ( split.vapScheme!=null && !surl.startsWith(split.vapScheme) ) split.vapScheme=null;
return format(split);
}
/**
* convenient method for getting a parameter in the URI.
* @param surl
* @param name parameter name.
* @param deft default value if the parameter is not found.
* @return
*/
public static String getParam( String surl, String name, String deft ) {
URISplit split= URISplit.parse(surl);
Map params= URISplit.parseParams( split.params );
String val= params.get( name );
if ( val==null ) val= deft;
return val;
}
/**
* returns group 1 if there was a match, null otherwise.
* @param s
* @param regex
* @return
*/
private static String magikPop(String s, String regex) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(s);
if (m.matches()) {
return m.group(1);
} else {
return null;
}
}
/**
* only split on the delimiter when we are not within the exclude delimiters. For example,
*
* x=getDataSet("http://autoplot.org/data/autoplot.cdf?Magnitude&noDep=T")&y=getDataSet('http://autoplot.org/data/autoplot.cdf?BGSEc&slice1=2')&sqrt(x)
*
* @param s the string to split.
* @param delim the delimiter to split on, for example the ampersand (&).
* @param exclude1 for example the single quote (')
* @param exclude2 for example the double quote (") Note URIs don't support these anyway.
* @return the split.
*/
public static String[] guardedSplit( String s, char delim, char exclude1, char exclude2 ) {
if ( delim=='_') throw new IllegalArgumentException("_ not allowed for delim");
StringBuilder scopyb= new StringBuilder(s.length());
char inExclude= (char)0;
for ( int i=0; i(char)0 ) c='_';
scopyb.append(c);
}
String[] ss= scopyb.toString().split(""+delim);
int i1= 0;
for ( int i=0; i j) result.resourceUriCarotPos -= (j + 1);
result.formatCarotPos = result.resourceUriCarotPos + result.vapScheme.length() + 1;
result.scheme = magikPop(result.surl, "([a-zA-Z\\+]+)\\:.*");
int iq= result.surl.indexOf('?');
if ( iq==-1 ) iq= result.surl.length();
try {
result.resourceUri = new URI(uriEncode(result.surl.substring(0,iq)));
result.scheme = result.resourceUri.getScheme();
} catch (URISyntaxException ex) {
// do nothing, this field may be null.
}
} else {
if (result.vapScheme == null && normalize ) {
result.formatCarotPos = result.resourceUriCarotPos;
}
result.surl = surl;
result.scheme = magikPop(result.surl, "([a-zA-Z\\+]+)\\:.*");
int iq= result.surl.indexOf('?');
if ( iq==-1 ) iq= surl.length();
try {
result.resourceUri = new URI(uriEncode(result.surl.substring(0,iq)));
result.scheme = result.resourceUri.getScheme();
} catch (URISyntaxException ex) {
// do nothing, this field may be null.
}
}
}
}
/**
* split the UI string into components, keeping track of the caret position
* when characters are inserted. This does not try to identify
* the vap scheme, since that might require interaction with the server to
* get mime type. This inserts the scheme "file://" when the scheme is
* absent.
* For example, the string http://www.example.com/data/myfile.nc?myVariable is split into:
* - vapScheme, vap+nc
*
- scheme, http
*
- authority, http://www.example.com
*
- path, the directory with http://www.example.com/data/
*
- file, the file, http://www.example.com/data/myfile.nc
*
- ext, the extension, .nc or null.
*
- params, myVariable or null.
*
- filters, the fragment of the URI following hash character.
*
* @param surl the string to parse
* @param caretPos the position of the caret, the relative position will be preserved through normalization in formatCaretPos
* @param normalize normalize the surl by adding implicit "vap", etc.
* @throws IllegalArgumentException
* @return the decomposed uri.
*/
public static URISplit parse( String surl, int caretPos, boolean normalize) {
if ( surl==null ) {
throw new NullPointerException("surl cannot be null");
}
logger.log( Level.FINE, "URISplit.parse(\"{0}\",{1},{2})", new Object[]{ surl, caretPos, normalize });
if ( surl.startsWith("file:/") && surl.endsWith(":") && surl.length()<11 && surl.charAt(surl.length()-3)=='/' ) { // kludge for file:///c: on Windows.
if ( caretPos==surl.length() ) caretPos++;
surl= surl+"/";
}
// finally, kludge for Unix ~/.
if ( surl.startsWith("~/") ) {
surl= System.getProperty("user.home") + surl.substring(1);
caretPos += ( System.getProperty("user.home").length() -1 );
}
if ( surl.startsWith("http://autoplot.org/autoplot.jnlp?") ) {
String[] popFront= new String[] { "http://autoplot.org/autoplot.jnlp?version=devel&", "http://autoplot.org/autoplot.jnlp?" };
for ( String s: popFront ) {
if ( surl.startsWith(s) ) {
surl= surl.substring(s.length());
caretPos= ( caretPos-1 ) {
int i2= file.indexOf('/',i);
if ( i2==-1 ) {
ext= file.substring(i);
} else {
ext= "";
}
} else {
ext = "";
}
if ( ext.length()>0 && ext.contains("&") ) { // catch errors when & is used instead of ?: .../WAV_2011227_SRV_V17.PKT&type=B
throw new IllegalArgumentException("The extension of a filename cannot contain ampersand (&): "+ surl);
}
}
String params = null;
int fileEnd=-1;
//int ipipe= file.indexOf("|");
//if ( ipipe>-1 ) {
// result.process= file.substring(ipipe);
// file= file.substring(0,ipipe);
//} else {
// result.process= "";
//}
int ihash;
if ( iquery==-1 ) {
ihash= rsurl.indexOf('#');
} else {
ihash= rsurl.indexOf('#',iquery);
}
if ( ihash==-1 ) ihash= rsurl.length();
if (file != null && iquery != -1) {
fileEnd = iquery;
params = rsurl.substring(iquery + 1,ihash);
} else {
if ( ieq>-1 && ( file==null || file.contains("=") && !( file.contains("(") || file.contains("{") ) ) ) { //TODO: this surely needs more attention.
// file:///home/jbf/fun/camE_spot5/2012/05/$(d,Y=2012,m=04)/$H$M$S.jpg
iquery = 0;
if ( rsurl.startsWith("file:///") ) { // old code used to insert file://, so we check for it here in case of old URIs.
params= rsurl.substring(8,ihash);
} else {
params= rsurl.substring(0,ihash);
}
} else {
iquery = rsurl.length();
fileEnd = rsurl.length();
}
}
//if ( params!=null && params.length()==0 ) { https://sourceforge.net/p/autoplot/bugs/1913/
// params=null;
//}
if ( ihash 1) result.authority = "tag:"+aTmp[1]; //see def on line 204 above
else result.authority = "tag:";
}
else{
int iauth = result.scheme.length() + 1;
while(iauth < rsurl.length() && rsurl.charAt(iauth) == '/'){
iauth++;
}
iauth = rsurl.indexOf('/', iauth);
if(iauth == -1){
iauth = rsurl.length();
}
if(rsurl.charAt(iauth - 1) == ':' && rsurl.charAt(iauth - 3) == ':'){
iauth = iauth - 2;
}
result.authority = rsurl.substring(0, iauth);
}
}
if ( ext!=null && ext.length()==0 ) ext=null;
if (file != null) {
i = rsurl.lastIndexOf('/', iquery);
if (i == -1) {
result.path = rsurl.substring(0, iquery);
result.file = rsurl.substring(0, iquery);
result.ext = ext;
} else {
String surlDir = rsurl.substring(0, i);
result.path = surlDir + "/";
result.file = rsurl.substring(0, fileEnd);
result.ext = ext;
}
}
result.params = params;
if ( "".equals(result.file) ) result.file=null;
return result;
}
private static int indexOf(String s, char ch, char ignoreBegin, char ignoreEnd) {
int i = s.indexOf(ch);
int i0 = s.indexOf(ignoreBegin);
int i1 = s.indexOf(ignoreEnd);
if (i != -1 && i0 < i && i < i1) {
i = -1;
}
return i;
}
/**
* Split the parameters (if any) into name,value pairs. URLEncoded parameters are decoded, but the string may be decoded
* already. Items without equals (=) are inserted as "arg_N"=name.
* @param params null or String containing the list of ampersand-delimited parameters.
* @return the map, which will be empty when there are no params.
*/
public static LinkedHashMap parseParams(String params) {
LinkedHashMap result = new LinkedHashMap<>();
if (params == null) {
return result;
}
if (params.trim().equals("")) {
return result;
}
params = URISplit.uriDecode(params);
// if ( params.contains("+") && params.contains(" ") ) { // this may be a problem. We know spaces are not encoded as pluses.
// System.err.println("params appear to be decoded already"); // logger okay
// } else {
// if ( params.contains("+") && !params.contains("%20") ) { // legacy
// params = params.replaceAll("+", " " );
// }
// params = URISplit.uriDecode(params);
// //params = params.replaceAll("\\+", " "); // in the parameters, plus (+) is the same as space ( ).
// }
String[] ss = params.split("&");
int argc = 0;
for (String s : ss) {
int j = indexOf(s, '=', '(', ')');
String name, value;
if (j == -1) {
name = s;
value = "";
name = name.replaceAll("%3D", "=" ); // https://sourceforge.net/tracker/?func=detail&aid=3049295&group_id=199733&atid=970682
result.put("arg_" + (argc++), name);
} else {
name = s.substring(0, j).trim();
value = s.substring(j + 1);
if ( name.equals( URISplit.PARAM_TIME_RANGE ) ) {
value= value.replaceAll("\\+", " ");
}
value = value.replaceAll("%3D", "=" ); // https://sourceforge.net/tracker/?func=detail&aid=3049295&group_id=199733&atid=970682
value = value.replaceAll("%26", "&");
result.put(name, value);
}
}
return result;
}
/**
* spaces and other URI syntax elements are URL-encoded.
* Note some calls of this routine should check for an empty string result
* and then set split.params=null instead of "", to avoid the extraneous
* question mark.
*
* @param parms
* @return "" or the parameters delimited by ampersands.
*/
public static String formatParams(Map parms) {
StringBuilder result = new StringBuilder("");
for ( Entry e: parms.entrySet() ) {
String key = (String) e.getKey();
if (key.startsWith("arg_")) {
String value= e.getValue();
if ( value!=null && !value.equals("")) {
result.append("&").append(value);
}
} else {
String value = (String) e.getValue();
if (value != null) {
if ( key.equals( URISplit.PARAM_TIME_RANGE ) ) {
value= value.replaceAll("\\s+","+");
}
value= value.replaceAll("&", "%26");
result.append("&").append(key).append("=").append(value);
} else {
result.append("&").append(key);
}
}
}
return (result.length() == 0) ? "" : result.substring(1);
}
/**
* format the URI using vapScheme, file and params.
* If file is missing but params is present, then return params:
* vap+cdaweb:ds=myds
* If file is present, then format with file and params:
* vap+cdf:file://tmp/my.cdf?myVar
* Else, just use the surl that is in there already.
* Note if split.params is non-null, it will be appended with a question mark, even if empty.
* @param split
* @return formatted URI.
*/
public static String format(URISplit split) {
String result = "";
if ( split.vapScheme!=null && split.vapScheme.length()>0 && !split.vapScheme.equals("vap") ) result= result + split.vapScheme + ":";
if ( split.file==null && split.params!=null ) {
result= result + split.params;
} else if ( split.file!=null ) {
result= result + split.file;
if (split.params != null ) { //&& split.params.length()>0 ) { This is needed for completions.
result += "?" + split.params;
}
} else if ( split.surl!=null ) {
result+= split.surl;
}
return result;
}
/**
* convenience method for creating URIs.
* @param vapScheme null or the data source scheme, such as "vap+das2server" or "vap+cdaweb"
* @param resourceUri null or the resource uri, such as "http://www-pw.physics.uiowa.edu/das/das2Server"
* @param args null or a map of arguments, including "arg_0" for a positional argument.
* @return the URI. If vapScheme is null, then the URI will be implicit.
* @see org.autoplot.jythonsupport#uri
*/
public static String format( String vapScheme, String resourceUri, Map args ) {
Map largs;
if ( args!=null ) {
largs= new LinkedHashMap(); //
for ( Entry e: args.entrySet() ) {
if ( e.getValue()==null ) {
largs.put( e.getKey(), "" );
} else {
largs.put( e.getKey(), String.valueOf(e.getValue()) );
}
}
} else {
largs= null;
}
if ( resourceUri==null ) {
if ( vapScheme==null ) {
throw new IllegalArgumentException("vapScheme must be specified when resourceUri is null");
}
if ( largs!=null ) {
return vapScheme + formatParams(largs);
} else {
return vapScheme;
}
} else {
URISplit split= URISplit.parse(resourceUri);
if ( vapScheme!=null ) {
split.vapScheme= vapScheme;
}
if ( largs!=null ) {
split.params= formatParams(largs);
}
return URISplit.format(split);
}
}
/**
* We need a standard way to detect if a string has already been URL encoded.
* The problem is we want valid URIs that are also readable, so just using
* simple encode/decode logic is not practical.
*
* This means:
* - no spaces
*
- contains %[0-9][0-9]
*
* @param surl the URI
* @return true if it appears to be encoded.
*/
public static boolean isUriEncoded( String surl ) {
boolean result= false;
// check for illegal characters.
if ( surl.contains(" ") ) result= false;
// check for encoded characters.
if ( Pattern.compile("%[0-9A-F][0-9A-F]").matcher(surl).find() ) result= true;
return result;
}
/**
* convert " " to "%20", etc, by looking for and encoding illegal characters.
* We can't just aggressively convert...
* @param surl the URI
* @return the URL-encoded URI
*/
public static String uriEncode(String surl) {
if ( isUriEncoded(surl) ) return surl;
surl = surl.replaceAll("%([^0-9])", "%25$1"); //%Y, %j, etc
surl = surl.replaceAll("\\%24", "\\$"); // What's this--seems backward. We like $'s in URIs...
surl = surl.replaceAll(" ", "%20" );
//surl = surl.replaceAll("#", "%23" );
//surl = surl.replaceAll("%", "%25" ); // see above
//surl = surl.replaceAll("&", "%26" );
//surl = surl.replaceAll("\\+", "%2B" );
//surl = surl.replaceAll("/", "%2F" );
//surl = surl.replaceAll(":", "%3A" );
//surl = surl.replaceAll(";", "%3B" );
surl = surl.replaceAll("<", "%3C");
surl = surl.replaceAll(">", "%3E");
//surl = surl.replaceAll("\\?", "%3F" );
surl = surl.replaceAll("\\[", "%5B"); // Windows appends these in temporary downloadf rte_1495358356
surl = surl.replaceAll("\\]", "%5D");
surl = surl.replaceAll("\\^", "%5E");
return surl;
}
/**
* convert "+" to " ", etc, by using URLDecoder and catching the UnsupportedEncodingException that will never occur.
* We have to be careful for elements like %Y than are
* not to be decoded.
* TODO: we need to use standard escape/unescape code, possibly changing %Y to $Y beforehand.
* @param s
* @return
*/
public static String uriDecode(String s) {
if ( !isUriEncoded(s) ) return s;
String surl= s;
// if ( surl.contains("+") && !surl.contains("%20") ) { // legacy
// surl = surl.replaceAll("+", " " );
// }
surl = surl.replaceAll("%20", " " );
//surl = surl.replaceAll("%23", "#" );
surl = surl.replaceAll("%25", "%" );
//surl = surl.replaceAll("%26", "&" );
surl = surl.replaceAll("%2B", "+" );
//surl = surl.replaceAll("%2F", "/" );
//surl = surl.replaceAll("%3A", ":" );
//surl = surl.replaceAll("%3B", ";" );
surl = surl.replaceAll("%3C", "<" );
surl = surl.replaceAll("%3E", ">" );
//surl = surl.replaceAll("%3F", "?" );
surl = surl.replaceAll("%5B", "\\[" ); // Windows appends these in temporary downloadf rte_1495358356
surl = surl.replaceAll("%5D", "\\]" );
surl = surl.replaceAll("%5E", "^" );
surl = surl.replaceAll("%5C", "\\\\" );
surl = surl.replaceAll("%7C", "|" );
return surl;
}
/**
* Helper method to get the timerange from the URI
* @param uri
* @return the DatumRange if "timerange=" is found, or null if not.
* @throws ParseException
*/
public static DatumRange parseTimeRange( String uri ) throws ParseException {
URISplit split= URISplit.parse(uri);
Map params= URISplit.parseParams(split.params);
String str= params.get( URISplit.PARAM_TIME_RANGE );
if ( str!=null ) {
DatumRange timerange= DatumRangeUtil.parseTimeRange( str );
return timerange;
} else {
return null;
}
}
@Override
public String toString() {
return "\nvapScheme: " + vapScheme + "\nscheme: " + scheme + "\nresourceUri: " + resourceUri + "\npath: " + path + "\nfile: " + file + "\next: " + ext + "\nparams: " + params + "\nfilters: "+filters + "\nsurl: " + surl + "\ncaretPos: " + resourceUriCarotPos + "\nformatCarotPos: " + formatCarotPos;
}
}