// Copyright (c) 2005 Dustin Sallings <dustin@spy.net> package net.spy.db; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.lang.reflect.Field; import java.math.BigDecimal; import java.net.URL; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Types; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import net.spy.util.CloseUtil; /** * Stub class for building a ResultSet from a URL. */ public class FileResultSetStub extends GenericResultSetStub { /** * Get an instance of FileResultSetStub. */ public FileResultSetStub(URL f, int maxResults) throws SQLException { super(); try { initFromURL(f, maxResults); } catch(IOException e) { SQLException toThrow=new SQLException( "Could not initialize results from " + f); toThrow.initCause(e); throw toThrow; } } private void initFromURL(URL u, int maxResults) throws SQLException, IOException { InputStream is=null; try { is=u.openStream(); LineNumberReader lnr=new LineNumberReader(new InputStreamReader(is)); MyMetaData mmd=new MyMetaData(lnr.readLine()); setMetaData(mmd); List<Object[]> results=new ArrayList<Object[]>(); String tmp=lnr.readLine(); while(tmp != null && results.size() < maxResults) { Object[] result=mmd.parseLine(tmp); results.add(result); tmp=lnr.readLine(); } setResults(results); } finally { CloseUtil.close(is); } } private static interface Parser { Object parseString(String s) throws Exception; } static abstract class PreParser implements Parser { public final Object parseString(String s) throws Exception { Object rv=null; String cleanedUp=cleanString(s); if(cleanedUp != null) { rv=subParse(cleanedUp); } return(rv); } private String cleanString(String s) { String rv=null; if(!s.equals("\\N")) { StringBuilder sb=new StringBuilder(s.length()); for(int i=0; i<s.length(); i++) { char c=s.charAt(i); switch(c) { case '\\': i++; char escaped=s.charAt(i); switch(escaped) { case 't': sb.append('\t'); break; case 'n': sb.append('\n'); break; default: sb.append('\\'); } break; default: sb.append(c); } } rv=sb.toString(); } return(rv); } protected abstract Object subParse(String s) throws Exception; } static final class NumberParser extends PreParser { @Override public Object subParse(String s) throws Exception { return(new BigDecimal(s)); } } static final class StringParser extends PreParser { @Override public Object subParse(String s) { return(s); } } abstract static class MultiDateParser extends PreParser { private SimpleDateFormat[] formats=null; public MultiDateParser(String[] formatStrings) { super(); formats=new SimpleDateFormat[formatStrings.length]; for(int i=0; i<formatStrings.length; i++) { formats[i]=new SimpleDateFormat(formatStrings[i]); formats[i].setLenient(false); } } protected long parseDate(String s) { long rv=0; for(int i=0; rv==0 && i<formats.length; i++) { try { rv=formats[i].parse(s).getTime(); } catch(ParseException e) { // skip } } return(rv); } } private static final class TimeParser extends MultiDateParser { public TimeParser() { super(new String[]{"HH:mm:ss.SSS", "HH:mm:ss"}); } @Override public Object subParse(String s) throws Exception { return(new java.sql.Time(parseDate(s))); } } private static final class DateParser extends MultiDateParser { public DateParser() { super(new String[]{ "yyyyMMdd", "yyyy-MM-dd", "yyyy/MM/dd", "dd/MM/yyyy"}); } @Override public Object subParse(String s) throws Exception { return(new java.sql.Date(parseDate(s))); } } private static final class TimestampParser extends MultiDateParser { public TimestampParser() { super(new String[]{ "yyyyMMdd'T'HH:mm:ss.SSS", "yyyyMMdd'T'HH:mm:ss", "yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'HHmmss", "yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss.SSS", "yyyy/MM/dd HH:mm:ss", "dd/MM/yyyy HH:mm:ss.SSS", "dd/MM/yyyy HH:mm:ss", }); } @Override public Object subParse(String s) throws Exception { return(new java.sql.Timestamp(parseDate(s))); } } static final class ParserFactory extends Object { private static ParserFactory instance=null; private Map<Integer, PreParser> parsers=null; private ParserFactory() { super(); parsers=new HashMap<Integer, PreParser>(); parsers.put(new Integer(Types.VARCHAR), new StringParser()); parsers.put(new Integer(Types.LONGVARCHAR), new StringParser()); parsers.put(new Integer(Types.INTEGER), new NumberParser()); parsers.put(new Integer(Types.BIGINT), new NumberParser()); parsers.put(new Integer(Types.DECIMAL), new NumberParser()); parsers.put(new Integer(Types.DOUBLE), new NumberParser()); parsers.put(new Integer(Types.FLOAT), new NumberParser()); parsers.put(new Integer(Types.NUMERIC), new NumberParser()); parsers.put(new Integer(Types.REAL), new NumberParser()); parsers.put(new Integer(Types.SMALLINT), new NumberParser()); parsers.put(new Integer(Types.TINYINT), new NumberParser()); parsers.put(new Integer(Types.TIMESTAMP), new TimestampParser()); parsers.put(new Integer(Types.DATE), new DateParser()); parsers.put(new Integer(Types.TIME), new TimeParser()); parsers.put(new Integer(Types.BIT), new PreParser() { @Override public Object subParse(String s) throws Exception { Boolean rv=Boolean.FALSE; try { if(Integer.parseInt(s) == 0) { rv=Boolean.FALSE; } else { rv=Boolean.TRUE; } } catch(NumberFormatException e) { rv=Boolean.valueOf(s); } return(rv); } }); } public static synchronized ParserFactory getInstance() { if(instance == null) { instance=new ParserFactory(); } return(instance); } public Parser getParser(int type) throws SQLException { Parser rv=parsers.get(new Integer(type)); if(rv == null) { throw new SQLException("Don't have a parser for " + TypeNames.getTypeName(type)); } return(rv); } } static final class MyMetaData implements ResultSetMetaData { private String[] names=null; private int[] types=null; public MyMetaData(String line) throws SQLException { super(); StringTokenizer st=new StringTokenizer(line, "\t"); names=new String[st.countTokens()]; types=new int[st.countTokens()]; int i=0; while(st.hasMoreTokens()) { String desc=st.nextToken(); StringTokenizer parts=new StringTokenizer(desc, ":"); names[i]=parts.nextToken(); types[i]=lookupType(parts.nextToken()); i++; } } private int lookupType(String typeName) throws SQLException { int rv=0; try { Field f=Types.class.getDeclaredField(typeName); rv=((Integer)f.get(null)).intValue(); } catch(Exception e) { SQLException toThrow=new SQLException( "Cannot look up type " + typeName); toThrow.initCause(e); throw toThrow; } return(rv); } public Object[] parseLine(String line) throws SQLException { // Parse the line ParserFactory pf=ParserFactory.getInstance(); StringTokenizer st=new StringTokenizer(line, "\t"); Object[] rv=new Object[names.length]; int i=0; while(st.hasMoreTokens()) { String toParse=st.nextToken(); try { rv[i]=pf.getParser(types[i]).parseString(toParse); } catch(SQLException e) { throw e; } catch(Exception e) { SQLException toThrow=new SQLException("Couldn't parse " + toParse + " as " + TypeNames.getTypeName(types[i])); toThrow.initCause(e); throw toThrow; } i++; } return(rv); } public int getColumnCount() throws SQLException { return(names.length); } public boolean isAutoIncrement(int col) throws SQLException { return(false); } public boolean isCaseSensitive(int col) throws SQLException { return(true); } public boolean isSearchable(int col) throws SQLException { return(false); } public boolean isCurrency(int col) throws SQLException { return(false); } public int isNullable(int col) throws SQLException { return(columnNoNulls); } public boolean isSigned(int col) throws SQLException { return(false); } public int getColumnDisplaySize(int col) throws SQLException { return(20); } public String getColumnLabel(int col) throws SQLException { return(names[col-1]); } public String getColumnName(int col) throws SQLException { return(names[col-1]); } public String getSchemaName(int col) throws SQLException { return("testSchema"); } public int getPrecision(int col) throws SQLException { return(0); } public int getScale(int col) throws SQLException { return(0); } public String getTableName(int col) throws SQLException { return("testTable"); } public String getCatalogName(int col) throws SQLException { return("testCatalog"); } public int getColumnType(int col) throws SQLException { return(types[col-1]); } public String getColumnTypeName(int col) throws SQLException { return(TypeNames.getTypeName(types[col-1])); } public boolean isReadOnly(int col) throws SQLException { return(true); } public boolean isWritable(int col) throws SQLException { return(false); } public boolean isDefinitelyWritable(int col) throws SQLException { return(false); } public String getColumnClassName(int col) throws SQLException { return("java.lang.Object"); } } }