View Javadoc

1   /***
2    * Copyright 2004 Steven Caswell
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.mungoknotwise.sqlcli;
17  
18  import java.sql.DatabaseMetaData;
19  import java.sql.ResultSet;
20  import java.sql.ResultSetMetaData;
21  import java.sql.SQLException;
22  import java.sql.Types;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import org.apache.commons.beanutils.DynaBean;
28  import org.apache.commons.beanutils.DynaProperty;
29  import org.apache.commons.beanutils.RowSetDynaClass;
30  import org.apache.commons.lang.StringUtils;
31  import org.apache.log4j.Logger;
32  
33  /***
34   * An implementation of the {@link ResultSetExtractor} that writes the results
35   * of a describe command to the provided print streams.
36   *
37   * @author  Steven Caswell
38   * @version $Id: PrintStreamedDescribeResultSetExtractor.java,v 1.4 2004/10/20 21:41:32 mungoknotwise Exp $
39   */
40  public class PrintStreamedDescribeResultSetExtractor
41    extends BasicPrintStreamed
42    implements ResultSetExtractor
43  {
44    
45    //----------------------------------------------------------------------------
46    // Static variables
47    //----------------------------------------------------------------------------
48    
49    private static Logger logger = Logger.getLogger("DEBUG." + PrintStreamedDescribeResultSetExtractor.class.getName());
50    
51    //----------------------------------------------------------------------------
52    // Static methods
53    //----------------------------------------------------------------------------
54    
55    //----------------------------------------------------------------------------
56    // Constants
57    //----------------------------------------------------------------------------
58    
59    private static final int COLUMN_COUNT = 4;
60    private static final int META_DATA_COLUMN_NAME_INDEX = 4;
61    private static final int META_DATA_COLUMN_TYPE_INDEX = 6;
62    private static final int META_DATA_DEFAULT_VALUE_INDEX = 13;
63    
64    private static final int NULL_VALUE_DISPLAY_SIZE = 4;
65    private static final int DEFAULT_VALUE_MINIMUM_DISPLAY_SIZE = 7;
66    
67    private static final int DISPLAY_COLUMN_NAME_INDEX = 0;
68    private static final int DISPLAY_COLUMN_TYPE_INDEX = 1;
69    private static final int DISPLAY_NULLABLE_INDEX = 2;
70    private static final int DISPLAY_DEFAULT_VALUE_INDEX = 3;
71    
72    private static final int COLUMN_NAME_INDEX = 4;
73    private static final int DATA_TYPE_INDEX = 5;
74    private static final int COLUMN_TYPE_INDEX = 6;
75    private static final int COLUMN_SIZE_INDEX = 7;
76    private static final int DECIMAL_DIGITS_INDEX = 9;
77    private static final int DEFAULT_VALUE_INDEX = 13;
78    private static final int NULLABLE_INDEX = 18;
79    
80    //----------------------------------------------------------------------------
81    // Instance variables
82    //----------------------------------------------------------------------------
83  
84    private Map attributes;
85    private int columnCount;
86    private int columnNameColumnSize;
87    private int columnTypeColumnSize;
88    private int defaultValueColumnSize;
89    private int[] displaySize;
90    private String fieldSeparator = "|";
91    private String headerSeparator = "+";
92    private int[] justify;
93    private String[] label;
94    
95    //----------------------------------------------------------------------------
96    // Constructors
97    //----------------------------------------------------------------------------
98    
99    /***
100    * Constructs a new instance of
101    * <code>PrintStreamedDescribeResultSetExtractor</code>.
102    */
103   public PrintStreamedDescribeResultSetExtractor()
104   {
105     this.attributes = new HashMap();
106   }
107   
108   //----------------------------------------------------------------------------
109   // Interface implementations
110   //----------------------------------------------------------------------------
111   //----------------------------------------------------------------------------
112   // Implementation of interface ResultSetExtractor
113   //----------------------------------------------------------------------------
114 
115   /***
116    * {@inheritDoc}
117    */
118   public Object extractData(final ResultSet resultSet) throws SQLException
119   {
120     if(resultSet == null)
121     {
122       throw new IllegalArgumentException("resultSet is null");
123     }
124     ResultSetMetaData metaData = resultSet.getMetaData();
125     if(logger.isDebugEnabled())
126     {
127       logger.debug("result set meta data " + (metaData == null ? "is" : "is not") + " null");
128     }
129     
130     RowSetDynaClass rsdc = new RowSetDynaClass(resultSet);
131     if(logger.isDebugEnabled())
132     {
133       logger.debug("row set dyna class " + (rsdc == null ? "is" : "is not") + " null");
134     }
135     DynaProperty[] properties = rsdc.getDynaProperties();
136     if(logger.isDebugEnabled())
137     {
138       logger.debug("properties length: " + properties.length);
139       for(int i = 0; i < properties.length; i++)
140       {
141         logger.debug("property name: " + properties[i].getName() + " class: " + properties[i].getType());
142       }
143     }
144     this.configureOutput(metaData, rsdc);
145     if(logger.isDebugEnabled())
146     {
147       logger.debug("output has been configured");
148     }
149     
150     boolean found = false;
151     boolean needsHeader = true;
152     boolean needsFooter = true;
153     
154     List rows = rsdc.getRows();
155     if(logger.isDebugEnabled())
156     {
157       logger.debug("row count: " + rows.size());
158     }
159     
160     for(Iterator iter = rows.iterator(); iter.hasNext();)
161     {
162       found = true;
163       if(needsHeader)
164       {
165         this.writeHeader();
166         needsHeader = false;
167       }
168       DynaBean row = (DynaBean) iter.next();
169       if(logger.isDebugEnabled())
170       {
171         logger.debug("row values");
172         for(int i = 0; i < properties.length; i++)
173         {
174           logger.debug("property '" + properties[i].getName() + "': " + row.get(properties[i].getName()));
175         }
176       }
177       
178       int columnSize = -1;
179       if(row.get("column_size") != null)
180       {
181         columnSize = ((Number) row.get("column_size")).intValue();
182       }
183       if(logger.isDebugEnabled())
184       {
185         logger.debug("column size: " + columnSize);
186       }
187       int decimalDigits = -1;
188       if(row.get("decimal_digits") != null)
189       {
190         decimalDigits = ((Number) row.get("decimal_digits")).intValue();
191       }
192       if(logger.isDebugEnabled())
193       {
194         logger.debug("decimal digits: " + decimalDigits);
195       }
196       int dataType = ((Number) row.get("data_type")).intValue();
197       String columnName = (String) row.get("column_name");
198       String columnType = (String) row.get("type_name");
199       String defaultValue = (String) row.get("column_def");
200       String isNullable = (String) row.get("is_nullable");
201       if(logger.isDebugEnabled())
202       {
203         logger.debug("column name: " + columnName);
204         logger.debug("column type: " + columnType);
205         logger.debug("default value: " + (defaultValue == null ? "null" : defaultValue));
206         logger.debug("is nullable: " + isNullable);
207       }
208       this.print(this.getFieldSeparator());
209       this.print(StringUtils.rightPad(columnName, this.displaySize[DISPLAY_COLUMN_NAME_INDEX]));
210       this.print(this.getFieldSeparator());
211       if(dataType == Types.DATE)
212       {
213         
214       }
215       else if(dataType == Types.TIME ||
216               dataType == Types.TIMESTAMP
217              )
218       {
219         columnType += "(" + columnSize + ")";
220       }
221       else
222       {
223         if(decimalDigits <= 0)
224         {
225           columnType += "(" + columnSize + ")";
226         }
227         else
228         {
229           columnType += "(" + columnSize + "," + decimalDigits + ")";
230         }
231       }
232       this.print(StringUtils.rightPad(columnType, this.displaySize[DISPLAY_COLUMN_TYPE_INDEX]));
233       this.print(this.getFieldSeparator());
234       this.print(StringUtils.rightPad(isNullable, this.displaySize[DISPLAY_NULLABLE_INDEX]));
235       this.print(this.getFieldSeparator());
236       this.print(StringUtils.rightPad(defaultValue == null ? "NULL" : defaultValue, this.displaySize[DISPLAY_DEFAULT_VALUE_INDEX]));
237       this.println(this.getFieldSeparator());
238     }
239     if(found)
240     {
241       this.writeFooter();
242     }
243     else
244     {
245       String tableName = (String) this.attributes.get(DescribeCommand.TABLE_NAME_KEY);
246       if(!StringUtils.isEmpty(tableName))
247       {
248         this.print("Table or view '");
249         this.print(tableName);
250         this.println("' does not exist");
251       }
252     }
253     
254     return null;
255   }
256 
257   /***
258    * {@inheritDoc}
259    */
260   public void setAttribute(final String key, final Object value)
261   {
262     this.attributes.put(key, value);
263   }
264 
265   //----------------------------------------------------------------------------
266   // Extends overrides
267   //----------------------------------------------------------------------------
268   //----------------------------------------------------------------------------
269   // Override of class Class1
270   //----------------------------------------------------------------------------
271   
272   
273   //----------------------------------------------------------------------------
274   // Public methods exposed by this class
275   //----------------------------------------------------------------------------
276 
277   //----------------------------------------------------------------------------
278   // Protected abstract methods
279   //----------------------------------------------------------------------------
280   
281   //----------------------------------------------------------------------------
282   // Protected methods for use by subclasses
283   //----------------------------------------------------------------------------
284   
285   //----------------------------------------------------------------------------
286   // Other methods
287   //----------------------------------------------------------------------------
288 
289   private void configureOutput(final ResultSetMetaData metaData, final RowSetDynaClass rsdc)
290     throws SQLException
291   {
292     if(metaData == null)
293     {
294       throw new IllegalArgumentException("metaData is null");
295     }
296     if(rsdc == null)
297     {
298       throw new IllegalArgumentException("rsds is null");
299     }
300     this.columnCount = COLUMN_COUNT;
301     
302     this.displaySize = new int[COLUMN_COUNT];
303     this.displaySize[DISPLAY_COLUMN_NAME_INDEX] = metaData.getColumnDisplaySize(META_DATA_COLUMN_NAME_INDEX); // column name display size
304     this.displaySize[DISPLAY_COLUMN_TYPE_INDEX] = metaData.getColumnDisplaySize(META_DATA_COLUMN_TYPE_INDEX); // column type display size
305     this.displaySize[DISPLAY_NULLABLE_INDEX] = NULL_VALUE_DISPLAY_SIZE; // is nullable display size
306     defaultValueColumnSize = metaData.getColumnDisplaySize(META_DATA_DEFAULT_VALUE_INDEX);
307     if(defaultValueColumnSize < DEFAULT_VALUE_MINIMUM_DISPLAY_SIZE)
308     {
309       defaultValueColumnSize = DEFAULT_VALUE_MINIMUM_DISPLAY_SIZE;
310     }
311     this.displaySize[DISPLAY_DEFAULT_VALUE_INDEX] = defaultValueColumnSize;
312     this.label = new String[]
313     {   "Column Name"
314       , "Data Type"
315       , "Null"
316       , "Default"
317     };
318     // Calculate more accurate column type size based on column type value
319     // and column size value
320     List rows = rsdc.getRows();
321     if(logger.isDebugEnabled())
322     {
323       logger.debug("rows size: " + (rows == null ? "null" : "" + rows.size()));
324     }
325     for(Iterator iter = rows.iterator(); iter.hasNext();)
326     {
327       DynaBean row = (DynaBean) iter.next();
328       if(logger.isDebugEnabled())
329       {
330         logger.debug("row == null? " + (row == null));
331       }
332       String typeName = (String) row.get("type_name");
333       if(logger.isDebugEnabled())
334       {
335         logger.debug("type name: " + typeName);
336       }
337       int dataType = -1;
338       if(row.get("data_type") != null)
339       {
340         dataType = ((Number) row.get("data_type")).intValue();
341       }
342       int columnSize = -1;
343       if(row.get("column_size") != null)
344       {
345         columnSize = ((Number) row.get("column_size")).intValue();
346       }
347       int decimalDigits = -1;
348       if(row.get("decimal_digits") != null)
349       {
350         decimalDigits = ((Number) row.get("decimal_digits")).intValue();
351       }
352       if(logger.isDebugEnabled())
353       {
354         logger.debug("data type: " + dataType);
355         logger.debug("column size: " + columnSize);
356         logger.debug("decimal digits: " + decimalDigits);
357       }
358       int columnSizeLength = 0;
359       if(dataType == Types.TIME ||
360          dataType == Types.TIMESTAMP
361          )
362       {
363         columnSizeLength = Integer.toString(columnSize).length();
364       }
365       else
366       {
367         if(decimalDigits <= 0)
368         {
369           columnSizeLength = Integer.toString(columnSize).length();
370         }
371         else
372         {
373           columnSizeLength = Integer.toString(columnSize).length() + 1 + Integer.toString(decimalDigits).length();
374         }
375       }
376       columnSizeLength += 2;
377       columnSizeLength += typeName.length();
378       if(this.displaySize[DISPLAY_COLUMN_TYPE_INDEX] < columnSizeLength)
379       {
380         this.displaySize[DISPLAY_COLUMN_TYPE_INDEX] = columnSizeLength;
381       }
382     }
383     if(logger.isDebugEnabled())
384     {
385       logger.debug("calculated column type display size: " + this.displaySize[DISPLAY_COLUMN_TYPE_INDEX]);
386     }
387   }
388   
389   private void configureOutput(final ResultSetMetaData metaData) throws SQLException
390   {
391     this.columnCount = COLUMN_COUNT;
392     
393     this.displaySize = new int[COLUMN_COUNT];
394     this.displaySize[DISPLAY_COLUMN_NAME_INDEX] = metaData.getColumnDisplaySize(META_DATA_COLUMN_NAME_INDEX); // column name display size
395     this.displaySize[DISPLAY_COLUMN_TYPE_INDEX] = metaData.getColumnDisplaySize(META_DATA_COLUMN_TYPE_INDEX); // column type display size
396     this.displaySize[DISPLAY_NULLABLE_INDEX] = NULL_VALUE_DISPLAY_SIZE; // is nullable display size
397     defaultValueColumnSize = metaData.getColumnDisplaySize(META_DATA_DEFAULT_VALUE_INDEX);
398     if(defaultValueColumnSize < DEFAULT_VALUE_MINIMUM_DISPLAY_SIZE)
399     {
400       defaultValueColumnSize = DEFAULT_VALUE_MINIMUM_DISPLAY_SIZE;
401     }
402     this.displaySize[DISPLAY_DEFAULT_VALUE_INDEX] = defaultValueColumnSize;
403     this.label = new String[]
404     {   "Column Name"
405       , "Data Type"
406       , "Null"
407       , "Default"
408     };
409   }
410   
411   private String getFieldSeparator()
412   {
413     return this.fieldSeparator;
414   }
415   
416   private String getHeaderSeparator()
417   {
418     return this.headerSeparator;
419   }
420   
421   private void writeHeader()
422   {
423     this.println("");
424     this.writeHeaderLine();
425 
426     for(int i = 1; i <= columnCount; i++)
427     {
428       this.print(this.getFieldSeparator());
429       
430       this.print(StringUtils.rightPad(this.label[i-1], this.displaySize[i-1]));
431     }
432     this.println(this.getFieldSeparator());
433     
434     this.writeHeaderLine();
435   }
436   
437   private void writeHeaderLine()
438   {
439     for(int i = 1; i <= this.columnCount; i++)
440     {
441       this.print(this.getHeaderSeparator());
442       this.print(StringUtils.repeat("-", this.displaySize[i-1]));
443     }
444     this.println(this.getHeaderSeparator());
445   }
446 
447   private void writeFooter()
448   {
449     this.writeHeaderLine();
450     this.println("");
451   }
452 
453   //----------------------------------------------------------------------------
454   // Member classes
455   //----------------------------------------------------------------------------
456   
457 }