1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
|
package uk.ac.bristol.star.cdf.util;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import uk.ac.bristol.star.cdf.CdfReader;
import uk.ac.bristol.star.cdf.record.Buf;
import uk.ac.bristol.star.cdf.record.CdfDescriptorRecord;
import uk.ac.bristol.star.cdf.record.CdfField;
import uk.ac.bristol.star.cdf.record.GlobalDescriptorRecord;
import uk.ac.bristol.star.cdf.record.OffsetField;
import uk.ac.bristol.star.cdf.record.Record;
import uk.ac.bristol.star.cdf.record.RecordFactory;
/**
* Utility to dump the records of a CDF file, optionally with field values.
* Intended to be used fro the command line via the <code>main</code> method.
* The function is roughly comparable to the <code>cdfirsdump</code>
* command in the CDF distribution.
*
* <p>The output can optionally be written in HTML format.
* The point of this is so that field values which represent pointers
* to records can be displayed as hyperlinks, which makes it very easy
* to chase pointers around the CDF file in a web browser.
*
* @author Mark Taylor
* @since 21 Jun 2013
*/
public class CdfDump {
private final CdfReader crdr_;
private final PrintStream out_;
private final boolean writeFields_;
private final boolean html_;
/**
* Constructor.
*
* @param crdr CDF reader
* @param out output stream for listing
* @param writeFields true to write field data as well as record IDs
* @param html true to write output in HTML format
*/
public CdfDump( CdfReader crdr, PrintStream out, boolean writeFields,
boolean html ) {
crdr_ = crdr;
out_ = out;
writeFields_ = writeFields;
html_ = html;
}
/**
* Does the work, writing output.
*/
public void run() throws IOException {
Buf buf = crdr_.getBuf();
RecordFactory recFact = crdr_.getRecordFactory();
long offset = 8; // magic number
long leng = buf.getLength();
long eof = leng;
CdfDescriptorRecord cdr = null;
GlobalDescriptorRecord gdr = null;
long gdroff = -1;
if ( html_ ) {
out_.println( "<html><body><pre>" );
}
for ( int ix = 0; offset < eof; ix++ ) {
Record rec = recFact.createRecord( buf, offset );
dumpRecord( ix, rec, offset );
if ( cdr == null && rec instanceof CdfDescriptorRecord ) {
cdr = (CdfDescriptorRecord) rec;
gdroff = cdr.gdrOffset;
}
if ( offset == gdroff && rec instanceof GlobalDescriptorRecord ) {
gdr = (GlobalDescriptorRecord) rec;
eof = gdr.eof;
}
offset += rec.getRecordSize();
}
if ( html_ ) {
out_.println( "<hr />" );
}
long extra = leng - eof;
if ( extra > 0 ) {
out_.println( " + " + extra + " bytes after final record" );
}
if ( html_ ) {
out_.println( "</pre></body></html>" );
}
}
/**
* Writes infromation about a single record to the output.
*
* @param index record index
* @param rec recor object
* @param offset byte offset into the file of the record
*/
private void dumpRecord( int index, Record rec, long offset ) {
StringBuffer sbuf = new StringBuffer();
if ( html_ ) {
sbuf.append( "<hr /><strong>" );
}
sbuf.append( index )
.append( ":\t" )
.append( rec.getRecordTypeAbbreviation() )
.append( "\t" )
.append( rec.getRecordType() )
.append( "\t" )
.append( rec.getRecordSize() )
.append( "\t" )
.append( formatOffsetId( offset ) );
if ( html_ ) {
sbuf.append( "</strong>" );
}
out_.println( sbuf.toString() );
// If required write the field values. Rather than list them
// for each record type, just obtain them by introspection.
if ( writeFields_ ) {
Field[] fields = rec.getClass().getFields();
for ( int i = 0; i < fields.length; i++ ) {
Field field = fields[ i ];
if ( isCdfRecordField( field ) ) {
String name = field.getName();
Object value;
try {
value = field.get( rec );
}
catch ( IllegalAccessException e ) {
throw new RuntimeException( "Reflection error", e );
}
out_.println( formatFieldValue( name, value,
isOffsetField( field ) ) );
}
}
}
}
/**
* Determines whether a given object field is a field of the CDF record.
*
* @param field field of java Record subclass
* @return true iff field represents a field of the corresponding CDF
* record type
*/
private boolean isCdfRecordField( Field field ) {
if ( field.getAnnotation( CdfField.class ) != null ) {
int mods = field.getModifiers();
assert Modifier.isFinal( mods )
&& Modifier.isPublic( mods )
&& ! Modifier.isStatic( mods );
return true;
}
else {
return false;
}
}
/**
* Determines whetehr a given object field represents a file offset.
*
* @param field field of java Record subclass
* @return true iff field represents a scalar or array file offset value
*/
private boolean isOffsetField( Field field ) {
return field.getAnnotation( OffsetField.class ) != null;
}
/**
* Formats a field name/value pair for output.
*
* @param name field name
* @param value field value
*/
private String formatFieldValue( String name, Object value,
boolean isOffset ) {
StringBuffer sbuf = new StringBuffer();
sbuf.append( spaces( 4 ) );
sbuf.append( name )
.append( ":" );
sbuf.append( spaces( 28 - sbuf.length() ) );
if ( value == null ) {
}
else if ( value.getClass().isArray() ) {
int len = Array.getLength( value );
if ( isOffset ) {
assert value instanceof long[];
long[] larray = (long[]) value;
for ( int i = 0; i < len; i++ ) {
if ( i > 0 ) {
sbuf.append( ", " );
}
sbuf.append( formatOffsetRef( larray[ i ] ) );
}
}
else {
for ( int i = 0; i < len; i++ ) {
if ( i > 0 ) {
sbuf.append( ", " );
}
sbuf.append( Array.get( value, i ) );
}
}
}
else if ( isOffset ) {
assert value instanceof Long;
sbuf.append( formatOffsetRef( ((Long) value).longValue() ) );
}
else {
sbuf.append( value.toString() );
}
return sbuf.toString();
}
/**
* Format a value for output if it represents a possible target of
* a pointer.
*
* @param offset pointer target value
* @return string for output
*/
private String formatOffsetId( long offset ) {
String txt = "0x" + Long.toHexString( offset );
return html_ ? "<a name='" + txt + "'>" + txt + "</a>"
: txt;
}
/**
* Format a value for output if it apparentl represents a pointer
* to a particular file offset.
*
* @param offset target file offset
* @return string for output
*/
private String formatOffsetRef( long offset ) {
String txt = "0x" + Long.toHexString( offset );
// Only format strictly positive values. In some circumstances
// -1 and 0 are used as special values indicating no reference exists.
// The first record in any case starts at 0x8 (after the magic numbers)
// so any such values can't be genuine offsets.
return ( html_ && offset > 0L )
? "<a href='#" + txt + "'>" + txt + "</a>"
: txt;
}
/**
* Construct a padding string.
*
* @param count number of spaces
* @return string composed only of <code>count</code> spaces
*/
static String spaces( int count ) {
StringBuffer sbuf = new StringBuffer( count );
for ( int i = 0; i < count; i++ ) {
sbuf.append( ' ' );
}
return sbuf.toString();
}
/**
* Does the work for the command line tool, handling arguments.
* Sucess is indicated by the return value.
*
* @param args command-line arguments
* @return 0 for success, non-zero for failure
*/
public static int runMain( String[] args ) throws IOException {
String usage = new StringBuffer()
.append( "\n Usage:" )
.append( CdfDump.class.getName() )
.append( " [-help]" )
.append( " [-verbose]" )
.append( " [-fields]" )
.append( " [-html]" )
.append( " <cdf-file>" )
.append( "\n" )
.toString();
// Process arguments.
List<String> argList = new ArrayList<String>( Arrays.asList( args ) );
int verb = 0;
File file = null;
boolean writeFields = false;
boolean html = false;
for ( Iterator<String> it = argList.iterator(); it.hasNext(); ) {
String arg = it.next();
if ( arg.equals( "-html" ) ) {
it.remove();
html = true;
}
else if ( arg.startsWith( "-h" ) ) {
it.remove();
System.out.println( usage );
return 0;
}
else if ( arg.equals( "-v" ) || arg.equals( "-verbose" ) ) {
it.remove();
verb++;
}
else if ( arg.equals( "+v" ) || arg.equals( "+verbose" ) ) {
it.remove();
verb--;
}
else if ( arg.startsWith( "-field" ) ) {
it.remove();
writeFields = true;
}
else if ( file == null ) {
it.remove();
file = new File( arg );
}
}
// Validate arguments.
if ( ! argList.isEmpty() ) {
System.err.println( "Unused args: " + argList );
System.err.println( usage );
return 1;
}
if ( file == null ) {
System.err.println( usage );
return 1;
}
// Configure and run.
LogUtil.setVerbosity( verb );
new CdfDump( new CdfReader( file ), System.out, writeFields, html )
.run();
return 0;
}
/**
* Main method. Use -help for arguments.
*/
public static void main( String[] args ) throws IOException {
int status = runMain( args );
if ( status != 0 ) {
System.exit( status );
}
}
}
|