Conditional Compile

G

Graham Matthews

If you don't own the libraries, I'd suggest using an adapter class that you
do own. Then, you're not quite so dependent on 3rd party libraries.

An adaptor class doens't help since I won't be able to compile the
adaptor class on any platform that doesn't have the jars in question.

graham
 
G

Graham Matthews

IP A Hill said:
Facade Pattern

It's not useful. I can't compile class X on any platform that doesn't
have jar Y (a jar I don't own, nor have the source too, nor can I
move). You can wrap X any way you like (e.g. Facade pattern),
and the problem still remains.

graham
 
G

Graham Matthews

Tor Iver Wilhelmsen said:
Unless it's a lot of code, use reflection (though you lose the
compile-time checks). Or invoke a class you compile once on the Mac
and ship as a .class for inclusion when building later.

I have been waiting for someone to suggest reflection. The cost of
using reflection is that I lose static typing. Moreover its a heavy
weight way of getting conditional compilation don't you think. To
my mind a simple conditional compilation scheme would be far
more desirable.

graham
 
D

dhek bhun kho

This is the best solution I have seen yet, other than simply using
directly to do conditional builds.
I must admit, I saw this one for the first time in the ant code for the
regex tasks. The only 'drawback' is that if you don't have a build file of
some kind you will get the ClassNotFoundException; but a "find | grep" on
"Class.forName" should fix that.

Greets.
 
T

Tim Tyler

:> Graham Matthews wrote:

:> > What happens if I don't have the source for this library.
:> > I just have the jar file, so I can't change the code in it to
:> > implement some new fangled interface.
:>
:> No, but as others have pointed out, you can make a thin wrapper
:> class that implements your interface. And you can use reflection
:> to load it so as to prevent class-loading problems.

: Yes I have to use reflection. That costs me type checking of this code.
: It is also a royal pain in the arse if I have a lot of code using this
: library. It's such a heavy-weight solution compared to conditional
: compilation.

It uses an existing mechanism. Conditional compilation would mean
changes to the JLS that make the language more complex.

I wouldn't describe that as particularly "light".
 
D

Dale King

Tim Tyler said:
: Graham Matthews wrote:

:> Try compiling the above code on a non OS-X platform. It will complain
:> that it can't find classes referenced in the "use_an_apple_extension"
:> bit.

: And the aforementioned stub library solves your compilation problem.

: I do it all the time. :)

What program do you use to make the stubs?

I know Dale wrote a program called CodeHusker to do this - are there
others?

I basically wrote that for you and never really finshed it off for public
consumption. Anybody is welcome to take what I started and run with it. I
attached the source at the end. It uses BCEL to do the job. Having gotten
more experience with BCEL, I would probably do it differently now.
I always believed that compiling against stubs ought to speed up loading
and searching rt.jar - and other large libraries people compile against.

These days it might also speed up things like code completion as well.

I'm not so convinced that it will, but am certainly interested to find out.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io_OutputStream;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.ConstantValue;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.SourceFile;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldGen;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;

/**
* @author kingd
* @version $Revision$ $Date$
*/
public class CodeHusker
{
private final ZipInputStream zinp;
private final ZipOutputStream zoutp;
private boolean keepResources = false;
private final InstructionList emptyInstructionList = new
InstructionList();


public CodeHusker( InputStream in, OutputStream out )
{
zinp = new ZipInputStream( in );
zoutp = new ZipOutputStream( out );
}

/**
* Sets the keepResources.
* @param keepResources The keepResources to set
*/
public void setKeepResources(boolean keepResources)
{
this.keepResources = keepResources;
}

public static void main(String[] args) throws IOException
{
InputStream in = new FileInputStream( args[0] );
OutputStream fos = new FileOutputStream( args[1] );

CodeHusker h = new CodeHusker( in, fos );
h.setKeepResources( false );

h.beginCodeRemoval();

System.out.println( "done" );
}

public void beginCodeRemoval() throws IOException
{
for( ZipEntry entry; ( entry = zinp.getNextEntry() ) != null; )
{
String name = entry.getName();

handleFile( name, zinp );
}
zinp.close();
zoutp.close();
}

private OutputStream createFile( String name ) throws IOException
{
final ZipEntry outEntry = new ZipEntry( name );
final CRC32 crc = new CRC32();

zoutp.putNextEntry( outEntry );

return new CheckedOutputStream( zoutp, crc )
{
public void close() throws IOException
{
outEntry.setCrc( crc.getValue() );
zoutp.closeEntry();
}
};
}

private void handleFile( String name, InputStream in )
throws IOException, ClassFormatError
{
if( name.endsWith( ".class" ) )
{
InputStream input = new FilterInputStream( in )
{
public void close() throws IOException
{
// ignore closes
}
};
OutputStream out = createFile( name );

handleClass( name, input, out );
}
else if( keepResources )
{
OutputStream out = createFile( name );

for( int c; ( c = in.read() ) != -1; )
{
out.write( c );
}
out.close();
}
}

private void handleClass( String name, InputStream in, OutputStream
out )
throws IOException, ClassFormatError
{
ClassParser parser = new ClassParser( in, name );
JavaClass cls = parser.parse();

ClassGen c = stripClass( cls );
c.getJavaClass().dump( out );
}

private ClassGen stripClass( JavaClass cls )
{
ConstantPoolGen origPool = new
onstantPoolGen( cls.getConstantPool() );

ClassGen c = new ClassGen( "", "", "", 0, new String[ 0 ] );

// ClassGen constructor unfortunately adds a SourceFile
// attribute by default, remove it and any other default attributes.
Attribute[] attrib = c.getAttributes();
for (int i = 0; i < attrib.length; i++)
{
Attribute attribute = attrib;
c.removeAttribute( attribute );
}

// Removing the attribute does not remove the strings
// from the constant pool, so let's start again fresh
// with a new constant pool.
ConstantPoolGen cp = new ConstantPoolGen();
c.setConstantPool( cp );

// Since we restarted the constant pool, we need to
// set everything that would have been set in the
// constructor.
c.setClassName( cls.getClassName() );
c.setSuperclassName( cls.getSuperclassName() );
c.setAccessFlags( cls.getAccessFlags() );
c.setMajor( cls.getMajor() );
c.setMinor( cls.getMinor() );

attrib = cls.getAttributes();
for( int i = 0; i < attrib.length; i++ )
{
Attribute attribute = attrib;
if( !( attribute instanceof SourceFile ) )
{
c.addAttribute( attribute );
}
}

String[] interfaces = cls.getInterfaceNames();
for( int i = 0; i < interfaces.length; i++ )
{
c.addInterface( interfaces );
}

Field[] flds = cls.getFields();

for (int i = 0; i < flds.length; i++)
{
Field field = flds;

// Lose any private fields
if( !field.isPrivate() )
{
Attribute[] fa = field.getAttributes();

for (int j = 0; j < fa.length; j++)
{
Attribute attribute = fa[j];
if( attribute instanceof ConstantValue )
{
ConstantValue cv = (ConstantValue)attribute;
int index = cv.getConstantValueIndex();
index =
cp.addConstant( origPool.getConstant( index ), origPool );
cv.setConstantValueIndex( index );
break;
}
}

FieldGen fg = new FieldGen( field, cp );

c.addField( fg.getField() );
}
}

Method[] methods = cls.getMethods();

for (int i = 0; i < methods.length; i++)
{
Method method = methods;
String name = method.getName();

// Remove any private methods except for private constructors
if( !method.isPrivate() || "<init>".equals( name ) )
{
MethodGen mg = new MethodGen( method, cls.getClassName(),
cp );
mg.stripAttributes( true );
mg.removeCodeAttributes();
mg.setInstructionList( new InstructionList() );

c.addMethod( mg.getMethod() );
}
}
return c;
}
}
 
J

John C. Bollinger

Graham said:
Yes I have to use reflection. That costs me type checking of this code.

It costs you only compile-time type checking of the arguments to the
constructor. If all the constructors used will implement a common
signature or signatures then you could achieve compile-time type
checking by wrapping the reflective instantiation in a suitable method,
which is probably a good idea in the first place. Since you would be
writing the adpater classes yourself, you would have control over the
constructor signatures. You get runtime type checking in any event.

After instantiation the object is assigned to a suitably typed reference
variable (typed according to the interface discussed earlier) which
provides for all the type checking you could get any other way (after
instantiation).
It is also a royal pain in the arse if I have a lot of code using this
library. It's such a heavy-weight solution compared to conditional
compilation.

In most libraries there are few objects that one needs to instantiate
directly. Writing adapters for those objects and suitable interfaces
and loading methods for those kinds of objects is not particularly
heavyweight, and it decouples your application from any specific
library. It certainly beats conditionally specifying class names and
method signatures, which you are otherwise likely to have to do when
substituting one library for another. The conditional approach is much
uglier and more difficult to follow when reading the source code, and it
spreads code maintenance across the whole source. Abstracting the
library implementation into one or a few classes is prettier, more
elegant, and easier to maintain.


John Bollinger
(e-mail address removed)
 
J

John C. Bollinger

Graham said:
An adaptor class doens't help since I won't be able to compile the
adaptor class on any platform that doesn't have the jars in question.

And you are developing on a platform(s) that doesn't have at least all
the features that your deployment platforms will have? I think you have
deeper problems than we are discussing if that is the case. You compile
the adaptor for a particular library on a system that has that library,
of course, and distribute the adaptor with your application without need
to consider whether the deployment platform has the library or not.
Even if you need to provide for compilation in the field you can easily
make compilation of the adapter package for each supported library
separate from compilation of the main application, and compile only
those pieces that are relevant under control of the build system.


John Bollinger
(e-mail address removed)
 
G

Graham Matthews

I"John C. Bollinger said:
And you are developing on a platform(s) that doesn't have at least all
the features that your deployment platforms will have?

Precisely. Some of us develop on Mac OS X, and others on Windoze.
The Windoze boys don't have the Mac jars.

graham
 
J

Jon A. Cruz

John said:
The point is not to wrap X but to wrap Y. The wrapper implements
whatever interface X requires, and it is not necessary to have Y
available to compile X. It is not even necessary to have the wrapper
available to compile X; only the interface is required. In other words,
X is decoupled from Y. That's generally regarded as a good thing, and
conditional compilation doesn't provide it -- conditional compilation
only allows you to select at compile time what library you wish to be
coupled to.


Just to clarify things here.

I use just that, and it is quite usefull to me.

I have just such an interface wrapping some of the MRJ classes. My app
does different things on Windows, Linux and Macintoshes. However, I
compile it all on one of my Linux boxes.

When my app starts up on a Mac, all the menus jump to the top of the
screen, I have the proper exit item, I hook the about and shutdown
events, all without ever having a single line of Apple code or library
on my machine where I compiled it.

And no conditional compilation either.
 
G

Graham Matthews

Tim Tyler said:
: Yes I have to use reflection. That costs me type checking of this code.
: It is also a royal pain in the arse if I have a lot of code using this
: library. It's such a heavy-weight solution compared to conditional
: compilation.

It uses an existing mechanism. Conditional compilation would mean
changes to the JLS that make the language more complex.

I wouldn't describe that as particularly "light".

I agree that given the current state of Java adding conditional
compilation is heavy. I am just surprised it wasn't in there from the
beginning.

But I guess different people's takes on the virtues of conditional
compilation are ... different.

graham
 
J

Jon A. Cruz

Graham said:
Yes I have to use reflection. That costs me type checking of this code.
It is also a royal pain in the arse if I have a lot of code using this
library. It's such a heavy-weight solution compared to conditional
compilation.

Ahhh. I think I see where you're missing things.


I'm not saying that you use reflection for each call. That would be a pain.

Instead, you use a single reflection call to load a platform-specific
class that implements your interface for that case.


So all the rest of your code is compiling to your interface, and keeps
all its type checking, etc.
 
J

John C. Bollinger

Graham said:
Precisely. Some of us develop on Mac OS X, and others on Windoze.
The Windoze boys don't have the Mac jars.

All the same, only the adapter classes need to be compiled on a platform
containing the appropriate library. That shouldn't be a problem because
you are doing compiles on every platform anyway (or the question
wouldn't have ever arisen). Everything else can be compiled anywhere,
and if necessary you can control whether to compile particular adapter
packages via your build tools. No need for conditional compilation, and
development and maintenance of the core software is significantly eased
by not having to worry about the differing requirements of different
libraries. That's what abstraction is all about.


John Bollinger
(e-mail address removed)
 
G

Graham Matthews

I"John C. Bollinger said:
All the same, only the adapter classes need to be compiled on a platform
containing the appropriate library. That shouldn't be a problem because
you are doing compiles on every platform anyway (or the question
wouldn't have ever arisen). Everything else can be compiled anywhere,
and if necessary you can control whether to compile particular adapter
packages via your build tools. No need for conditional compilation, and
development and maintenance of the core software is significantly eased
by not having to worry about the differing requirements of different
libraries. That's what abstraction is all about.

I guess I don't see it this way. I would have abstracted the differing
libraries using an interface, even when using conditional compilation.
Whether you abstract like this is orthogonal to how you build. I would
have just manged the build process via conditonal compilation, since
the alternatives (e.g. an ant build script) involve more work.

graham
 
J

Jon A. Cruz

Graham said:
Precisely. Some of us develop on Mac OS X, and others on Windoze.
The Windoze boys don't have the Mac jars.

Again, with a stub jar, the Windoze boys don't need the Mac jars to
compile for the Mac.

Just use the stubs when compiling, don't deliver them.
 
J

Jim Sculley

Jon said:
Again, with a stub jar, the Windoze boys don't need the Mac jars to
compile for the Mac.

Just use the stubs when compiling, don't deliver them.

The Limewire folks have a stub JAR for the MRJ classes as well.

Jim S.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,586
Members
45,097
Latest member
RayE496148

Latest Threads

Top