Created
May 23, 2013 03:12
-
-
Save peteroupc/5632549 to your computer and use it in GitHub Desktop.
A Java utility class for parsing query strings.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.upokecenter.util; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
// Written by Peter O. in 2013. | |
// Any copyright is dedicated to the Public Domain. | |
// http://creativecommons.org/publicdomain/zero/1.0/ | |
// | |
// If you like this, you should donate to Peter O. | |
// at: http://upokecenter.com/d/ | |
// | |
public final class QueryStringHelper | |
{ | |
private QueryStringHelper(){} | |
private static String[] SplitAt(String s, String delimiter){ | |
if(delimiter==null || | |
delimiter.length()==0)throw new IllegalArgumentException(); | |
if(s==null || s.length()==0)return new String[]{""}; | |
int index=0; | |
boolean first=true; | |
ArrayList<String> strings=null; | |
int delimLength=delimiter.length(); | |
while(true){ | |
int index2=s.indexOf(delimiter,index); | |
if(index2<0){ | |
if(first)return new String[]{s}; | |
strings.add(s.substring(index)); | |
break; | |
} else { | |
if(first) { | |
strings=new ArrayList<String>(); | |
first=false; | |
} | |
String newstr=s.substring(index,index2); | |
strings.add(newstr); | |
index=index2+delimLength; | |
} | |
} | |
return strings.toArray(new String[]{}); | |
} | |
private static int ToHexNumber(int c) { | |
if(c>='A' && c<='Z') | |
return 10+c-'A'; | |
else if(c>='a' && c<='z') | |
return 10+c-'a'; | |
else if (c>='0' && c<='9') | |
return c-'0'; | |
return -1; | |
} | |
private static String PercentDecodeUTF8(String str){ | |
int len=str.length(); | |
boolean percent=false; | |
for(int i=0;i<len;i++){ | |
char c=str.charAt(i); | |
if(c=='%') { | |
percent=true; | |
} else if(c>=0x80) // Non-ASCII characters not allowed | |
return null; | |
} | |
if(!percent)return str;// return early if there are no percent decodings | |
int cp=0; | |
int bytesSeen=0; | |
int bytesNeeded=0; | |
int lower=0x80; | |
int upper=0xBF; | |
int markedPos=-1; | |
StringBuilder retString=new StringBuilder(); | |
for(int i=0;i<len;i++){ | |
int c=str.charAt(i); | |
if(c=='%'){ | |
if(i+2<len){ | |
int a=ToHexNumber(str.charAt(i+1)); | |
int b=ToHexNumber(str.charAt(i+2)); | |
if(a>=0 && b>=0){ | |
b=(byte) (a*16+b); | |
i+=2; | |
// b now contains the byte read | |
if(bytesNeeded==0){ | |
// this is the lead byte | |
if(b<0x80){ | |
retString.append((char)b); | |
continue; | |
} else if(b>=0xc2 && b<=0xdf){ | |
markedPos=i; | |
bytesNeeded=1; | |
cp=b-0xc0; | |
} else if(b>=0xe0 && b<=0xef){ | |
markedPos=i; | |
lower=(b==0xe0) ? 0xa0 : 0x80; | |
upper=(b==0xed) ? 0x9f : 0xbf; | |
bytesNeeded=2; | |
cp=b-0xe0; | |
} else if(b>=0xf0 && b<=0xf4){ | |
markedPos=i; | |
lower=(b==0xf0) ? 0x90 : 0x80; | |
upper=(b==0xf4) ? 0x8f : 0xbf; | |
bytesNeeded=3; | |
cp=b-0xf0; | |
} else { | |
// illegal byte in UTF-8 | |
retString.append('\uFFFD'); | |
continue; | |
} | |
cp<<=(6*bytesNeeded); | |
continue; | |
} else { | |
// this is a second or further byte | |
if(b<lower || b>upper){ | |
// illegal trailing byte | |
cp=bytesNeeded=bytesSeen=0; | |
lower=0x80; | |
upper=0xbf; | |
i=markedPos; // reset to the last marked position | |
retString.append('\uFFFD'); | |
continue; | |
} | |
// reset lower and upper for the third | |
// and further bytes | |
lower=0x80; | |
upper=0xbf; | |
bytesSeen++; | |
cp+=(b-0x80)<<(6*(bytesNeeded-bytesSeen)); | |
markedPos=i; | |
if(bytesSeen!=bytesNeeded) { | |
// continue if not all bytes needed | |
// were read yet | |
continue; | |
} | |
int ret=cp; | |
cp=0; | |
bytesSeen=0; | |
bytesNeeded=0; | |
// append the Unicode character | |
retString.appendCodePoint(ret); | |
continue; | |
} | |
} | |
} | |
} | |
if(bytesNeeded>0){ | |
// we expected further bytes here, | |
// so emit a replacement character instead | |
bytesNeeded=0; | |
retString.append('\uFFFD'); | |
} | |
// append the code point as is (we already | |
// checked for ASCII characters so this will | |
// be simple | |
retString.append((char)(c&0xFF)); | |
} | |
if(bytesNeeded>0){ | |
// we expected further bytes here, | |
// so emit a replacement character instead | |
bytesNeeded=0; | |
retString.append('\uFFFD'); | |
} | |
return retString.toString(); | |
} | |
public static List<String[]> ParseQueryString( | |
String input | |
){ | |
return ParseQueryString(input,null); | |
} | |
public static List<String[]> ParseQueryString( | |
String input, String delimiter | |
){ | |
if((input)==null)throw new NullPointerException("input"); | |
if(delimiter==null) { | |
// set default delimiter to ampersand | |
delimiter="&"; | |
} | |
// Check input for non-ASCII characters | |
for(int i=0;i<input.length();i++){ | |
if(input.charAt(i)>0x7F) | |
throw new IllegalArgumentException("input contains a non-ASCII character"); | |
} | |
// split on delimiter | |
String[] strings=SplitAt(input,delimiter); | |
ArrayList<String[]> pairs=new ArrayList<String[]>(); | |
for(String str : strings){ | |
if(str.length()==0) { | |
continue; | |
} | |
// split on key | |
int index=str.indexOf('='); | |
String name=str; | |
String value="";// value is empty if there is no key | |
if(index>=0){ | |
name=str.substring(0,index); | |
value=str.substring(index+1); | |
} | |
name=name.replace('+',' '); | |
value=value.replace('+',' '); | |
String[] pair=new String[]{name,value}; | |
pairs.add(pair); | |
} | |
for(String[] pair : pairs){ | |
// percent decode the key and value if necessary | |
pair[0]=PercentDecodeUTF8(pair[0]); | |
pair[1]=PercentDecodeUTF8(pair[1]); | |
} | |
return pairs; | |
} | |
private static String[] GetKeyPath(String s){ | |
int index=s.indexOf('['); | |
if(index<0){// start bracket not found | |
return new String[]{s}; | |
} | |
ArrayList<String> path=new ArrayList<String>(); | |
path.add(s.substring(0,index)); | |
index++;// move to after the bracket | |
while(true){ | |
int endBracket=s.indexOf(']',index); | |
if(endBracket<0){ // end bracket not found | |
path.add(s.substring(index)); | |
break; | |
} | |
path.add(s.substring(index,endBracket)); | |
index=endBracket+1; // move to after the end bracket | |
index=s.indexOf('[',index); | |
if(index<0){// start bracket not found | |
break; | |
} | |
index++;// move to after the start bracket | |
} | |
return path.toArray(new String[]{}); | |
} | |
private static boolean IsList(Map<String,Object> dict){ | |
if(dict==null)return false; | |
int index=0; | |
int count=dict.size(); | |
if(count==0)return false; | |
while(true){ | |
if(index==count) | |
return true; | |
String indexString=Integer.toString(index); | |
if(!dict.containsKey(indexString)){ | |
return false; | |
} | |
index++; | |
} | |
} | |
private static List<Object> ConvertToList(Map<String,Object> dict){ | |
ArrayList<Object> ret=new ArrayList<Object>(); | |
int index=0; | |
int count=dict.size(); | |
while(index<count){ | |
String indexString=Integer.toString(index); | |
ret.add(dict.get(indexString)); | |
index++; | |
} | |
return ret; | |
} | |
private static void ConvertLists(List<Object> dict){ | |
for(int i=0;i<dict.size();i++){ | |
@SuppressWarnings("unchecked") | |
Map<String,Object> value=((dict.get(i) instanceof Map<?,?>) ? (Map<String,Object>)dict.get(i) : null); | |
// A list contains only indexes 0, 1, 2, and so on, | |
// with no gaps. | |
if(IsList(value)){ | |
List<Object> newList=ConvertToList(value); | |
dict.set(i,newList); | |
ConvertLists(newList); | |
} else if(value!=null){ | |
// Convert the list's descendents | |
// if they are lists | |
ConvertLists(value); | |
} | |
} | |
} | |
private static void ConvertLists(Map<String,Object> dict){ | |
for(String key : new ArrayList<String>(dict.keySet())){ | |
@SuppressWarnings("unchecked") | |
Map<String,Object> value=((dict.get(key) instanceof Map<?,?>) ? (Map<String,Object>)dict.get(key) : null); | |
// A list contains only indexes 0, 1, 2, and so on, | |
// with no gaps. | |
if(IsList(value)){ | |
List<Object> newList=ConvertToList(value); | |
dict.put(key,newList); | |
ConvertLists(newList); | |
} else if(value!=null){ | |
// Convert the dictionary's descendents | |
// if they are lists | |
ConvertLists(value); | |
} | |
} | |
} | |
public static Map<String,Object> QueryStringToDict(String query){ | |
return QueryStringToDict(query,"&"); | |
} | |
public static Map<String,Object> QueryStringToDict(String query, String delimiter){ | |
Map<String,Object> root=new HashMap<String,Object>(); | |
for(String[] keyvalue : ParseQueryString(query,delimiter)){ | |
String[] path=GetKeyPath(keyvalue[0]); | |
Map<String,Object> leaf=root; | |
for(int i=0;i<path.length-1;i++){ | |
if(!leaf.containsKey(path[i])){ | |
// node doesn't exist so add it | |
Map<String,Object> newLeaf=new HashMap<String,Object>(); | |
leaf.put(path[i],newLeaf); | |
leaf=newLeaf; | |
} else { | |
@SuppressWarnings("unchecked") | |
Map<String,Object> o=((leaf.get(path[i]) instanceof Map<?,?>) ? (Map<String,Object>)leaf.get(path[i]) : null); | |
if(o!=null){ | |
leaf=o; | |
} else { | |
// error, not a dictionary | |
leaf=null; | |
break; | |
} | |
} | |
} | |
if(leaf!=null){ | |
leaf.put(path[path.length-1],keyvalue[1]); | |
} | |
} | |
// Convert array-like dictionaries to lists | |
ConvertLists(root); | |
return root; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment