uk.ac.starlink.gbin.GbinObjectReader Maven / Gradle / Ivy
Show all versions of stil Show documentation
package uk.ac.starlink.gbin;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Provides read services for an object implementing the
* gaia.cu1.tools.dal.gbin.GbinReader
interface.
* This object can read the elements stored in a GBIN file.
*
* Access is via reflection, so I don't need GaiaTools on the classpath
* at build time, for several reasons:
*
* - The GbinReader class is targeted at Java 1.7, and at time of
* writing STIL is targeted at java 1.5
* - GbinReader and its dependencies are probably large and complicated
* (though I haven't actually checked that)
* - To use this class for most actual GBIN files, you're going to
* need Gaia data model classes on the classpath which I have no
* intention of packaging with STIL. If you have the data model
* classes on the path, there's a good chance that you've got
* the relevant bits of GaiaTools as well.
* - It can benefit from future GbinReader implementations without
* requiring updates to the code in STIL (as long as the basic
* interfaces don't change).
*
*
* @author Mark Taylor
* @since 13 Aug 2014
*/
public class GbinObjectReader {
private final Object gbinReaderObj_;
private final Method hasNextMethod_;
private final Method nextMethod_;
private static final Object[] ARGS0 = new Object[ 0 ];
private static boolean isGaiaToolsInit_;
private static final Logger logger_ =
Logger.getLogger( "uk.ac.starlink.gbin" );
/**
* Constructor.
*
* @param gbinReaderObj object implementing
* gaia.cu1.tools.dal.gbin.GbinReader
* @throws IllegalArgumentException if gbinReaderObj
* doesn't appear to be a GbinReader
*/
public GbinObjectReader( Object gbinReaderObj ) {
gbinReaderObj_ = gbinReaderObj;
Class> clazz = gbinReaderObj.getClass();
hasNextMethod_ = getNoArgMethod( clazz, "hasNext", boolean.class );
nextMethod_ = getNoArgMethod( clazz, "next", null );
}
/**
* Indicates whether this reader can read another element.
*
* @return true if another record will be returned by next
*/
public boolean hasNext() throws IOException {
return Boolean.TRUE.equals( invokeNoArgMethod( hasNextMethod_ ) );
}
/**
* Reads the next record.
*
* @return object implementing gaia.cu1.tools.dm.GaiaRoot
*/
public Object next() throws IOException {
return invokeNoArgMethod( nextMethod_ );
}
/**
* Invokes a no-arg method on this reader's GbinReader object,
* translating exceptions appropriately.
*
* @param method method to invoke with no arguments
* @return method return value
* @throws IOException with an informative method if something
* went wrong
*/
private Object invokeNoArgMethod( Method method ) throws IOException {
try {
return method.invoke( gbinReaderObj_, ARGS0 );
}
catch ( IllegalAccessException e ) {
throw new AssertionError( e );
}
catch ( IllegalArgumentException e ) {
throw new AssertionError( e );
}
catch ( InvocationTargetException e ) {
Throwable targetEx = e.getTargetException();
if ( targetEx instanceof ClassNotFoundException ) {
throw (IOException)
new IOException( "Missing GBIN object class"
+ " - probably lacking DM jars" )
.initCause( e );
}
else {
throw (IOException)
new IOException( targetEx.getMessage() )
.initCause( e );
}
}
}
/**
* Returns a public instance method with no arguments and a given
* signature for a given class.
*
* @param clazz class to query
* @param name method name
* @param retClazz method return type
* @return method object
* @throws IllegalArgumentException with informative message
* if method does not exist
*/
private static Method getNoArgMethod( Class> clazz, String name,
Class> retClazz ) {
Method method;
try {
method = clazz.getMethod( name );
int mods = method.getModifiers();
if ( Modifier.isStatic( mods ) ||
! Modifier.isPublic( mods ) ||
( retClazz != null &&
! retClazz.equals( method.getReturnType() ) ) ) {
method = null;
}
}
catch ( NoSuchMethodException e ) {
method = null;
}
if ( method == null ) {
throw new IllegalArgumentException( "Object of class "
+ clazz.getName()
+ " is not a GbinReader"
+ " (no suitable " + name + "()"
+ " method)" );
}
return method;
}
/**
* Attempts to construct a GbinObjectReader that can read records
* from a given input stream.
*
* @param in input stream containing a GBIN file
* @return gbin object reader
*/
public static GbinObjectReader createReader( InputStream in )
throws IOException {
initGaiaTools();
return new GbinObjectReader( createGbinReaderObject( in ) );
}
/**
* Indicates whether a buffer containing the first few bytes of a
* file look like a GBIN magic number.
*
* Note this has not been exhaustively tested with all known
* GBIN variants.
*
* @param intro first few bytes to test
* @return true if buffer looks like it could be the start of a GBIN file
*/
public static boolean isMagic( byte[] intro ) {
if ( intro.length < 13 ) {
return false;
}
long magic = ((intro[ 7 ] & 0xffL) << 56)
| ((intro[ 6 ] & 0xffL) << 48)
| ((intro[ 5 ] & 0xffL) << 40)
| ((intro[ 4 ] & 0xffL) << 32)
| ((intro[ 3 ] & 0xffL) << 24)
| ((intro[ 2 ] & 0xffL) << 16)
| ((intro[ 1 ] & 0xffL) << 8)
| ((intro[ 0 ] & 0xffL) << 0);
/* Pre-V3 GBIN file - no magic number, but we can see if it's
* a java serialization stream. */
if ( ( magic & 0xffff ) == 0xedac ) {
int jserVers = ((intro[ 2 ] & 0xff) << 8)
| ((intro[ 3 ] & 0xff) << 0);
logger_.info( "Java serialised stream, may be GBIN v2 file"
+ " (java serialization version " + jserVers + ")" );
return true;
}
/* V3 or later GBIN file - see GAIA-C1-TN-ESAC-AH-004-1. */
else if ( magic == 0x1a0a0d4e49424789L ) {
int gbinVers = ((intro[ 9 ] & 0xff) << 24)
| ((intro[ 10 ] & 0xff) << 16)
| ((intro[ 11 ] & 0xff) << 8)
| ((intro[ 12 ] & 0xff) << 0);
logger_.info( "Post-v2 GBIN file identified"
+ " (GBIN version " + gbinVers + ")" );
return true;
}
/* No known GBIN format. */
return false;
}
/**
* Performs GaiaTools setup required before GbinReaders are used etc.
* Called by {@link #createReader createReader}.
* Calls after the first one do nothing, but are harmless and cheap.
* Uses reflection.
*/
public static synchronized void initGaiaTools() {
if ( ! isGaiaToolsInit_ ) {
String loaderClassName = "gaia.cu1.tools.util.props.PropertyLoader";
String loaderMethodName = "load";
String sig = loaderClassName + "." + loaderMethodName + "()";
logger_.info( "Invoking " + sig );
try {
Class.forName( loaderClassName )
.getMethod( loaderMethodName )
.invoke( null, new Object[ 0 ] );
}
catch ( Throwable e ) {
logError( Level.WARNING, "Failed to invoke " + sig, e );
}
isGaiaToolsInit_ = true;
}
}
/**
* Constructs a GbinReader from a given input stream.
* Uses reflection to invoke
* gaia.cu1.tools.dal.gbin.GbinFactory.getGbinReader()
.
*
* @param in input stream assumed to contain a GBIN file
* @return object implementing
* gaia.cu1.tools.dal.gbin.GbinReader
interface
* @throws IOException with informative message if something goes wrong,
* including reflection trouble
*/
public static Object createGbinReaderObject( InputStream in )
throws IOException {
try {
Class> factClazz =
Class.forName( "gaia.cu1.tools.dal.gbin.GbinFactory" );
Method getReaderMethod =
factClazz.getMethod( "getGbinReader", InputStream.class );
try {
return getReaderMethod.invoke( null, in );
}
catch ( IllegalArgumentException e ) {
throw (IOException)
new IOException( "Problem with GaiaTools classes" )
.initCause( e );
}
}
catch ( ClassNotFoundException e ) {
throw (IOException)
new IOException( "GaiaTools classes not available?" )
.initCause( e );
}
catch ( NoSuchMethodException e ) {
throw (IOException)
new IOException( "Problem with GaiaTools classes" )
.initCause( e );
}
catch ( IllegalAccessException e ) {
throw (IOException)
new IOException( "Problem with GaiaTools classes" )
.initCause( e );
}
catch ( InvocationTargetException e ) {
throw (IOException)
new IOException( e.getTargetException().getMessage() )
.initCause( e );
}
}
/**
* Write a logging message including an exception.
* This is a drop-in replacement for the method
* {@link java.util.logging.Logger#log(java.util.logging.Level,
* String, Throwable)};
* it's required because loading the GaiaTools classes seems
* to mess up the system configuration done by stilts/topcat on startup,
* so this implementation includes the actual exception stringification
* in the log message itself, as well as passing the exception
* itself to the logging system (which seems to get ignored after loading
* GaiaTools, even with stilts -debug).
* Exception text is also unlikely to frighten GBIN-competent users.
*
* @param level logging level
* @param msg basic log message
* @param error exception
*/
public static void logError( Level level, String msg, Throwable error ) {
logger_.log( level, msg + " - " + error, error );
}
}