You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by Laurie Harper <zo...@holoweb.net> on 2002/09/11 02:07:38 UTC

Re: [OT] preprocessing java source

This is pretty off topic so we should probably take it off list if you're
interested in discussing these ideas further... Feel free to reply privately
though.

On 9/9/02 5:24 AM, "didge" <di...@san.rr.com> wrote:
> The downsides I mention are the result of Java's lack of an integrated
> preprocessing facility.  I bet the Java authors presumed that since Java was
> platform independent, there was no need for a preprocessor and they never
> forsaw the growth that Java would undergo nor that it would one day develop
> into multiple, slightly incompatible platforms.

Not true, they're downsides of having a pre-processor at all. Not providing
one in Java was a concious decision based on years of experience dealing
with C / C++ and other languages.
 
> There are legitimate problems with preprocessing, but there is sometimes no
> other alternative.  Love it or hate it, preprocessing is a necessity.

Sometimes, yes, but you can almost always do what you need using solid
abstractions to encapsulate the platform dependencies and selecting the
appropriate implementation of that abstraction, as Dominique pointed out.
 
> For some problems, reflection, while it would work, is vastly inferior to
> preprocessing for handling platform specific differences, especially with
> regard to performance.  And for other problems, reflection is not even an
> option, especially where there is a massive api change, change to the
> language itself or an change that results in incompatible api.

Performance isn't really a problem here, since you should only need to do
anything dynamically once -- the first time you need to determine which
implementation to use. API changes are a good example of where this
technique is useful; your code talks to the abstract API and concrete
implementations underneath deal with the different APIs.

Changes to the language itself are a different, though related, problem.
You'd abstract out the dependency on the language feature and then select
the implementation at compile time. That way, you minimise the amount of
code that's platform specific.

> To wrapper an api with reflection, you must implement a complex dynamic
> facade to wrap the platform dependent api, since no platform dependent api
> can be directly referenced by the facade itself.  Every delegated method
> call is dynamically invoked, with significant overhead.

Sure, you could do it that way. But if the façade is just an interface and
you use a factory to obtain an implementation, you only have to do anything
dynamic once -- and there's really little to no complexity involved.
 
> The facade will have to declare that it supports the all platform dependent
> methods declared by the class to be type compatible (and only if the class
> is not final!), yet only a subset will ever actually be implemented for any
> given platform.  If a client accidentally invokes an unimplemented method on
> the facade, a runtime exception is the most likely result.  Ouch!  You'll
> may never find out if your api users are missusing your apis until their app
> is coverage tested (if you're lucky) or more likely, deployed!

There's a complexity issue if the capabilities of the API are sufficiently
different, for sure. The trick is to separate the abstractions of common
functionality from truly platform dependent ones.

Either there's N ways to achieve X and all the client code knows is that
they call a single common abstraction, or there are features that can't be
implemented on some platforms at all. The latter case requires a different
approach but can usually be covered by a capabilities discovery abstraction.

> In addition, the're at least a few issues in Java that cannot be resolved
> (or at least in a worthwhile fasion) short of preprocessing.
> 1. Non-backaward compatible language changes.  There have been remarkably
> few language changes in Java, for better or worse.  Assertions, however,
> will definitely prevent your code from compiling or running with pre-JDK1.4
> releases.  Reflection cannot help you here.

True, but abstraction might, depending on what you're trying to achieve.
Like I said before, though, fundamental language changes fall into a
slightly different category. I'd agree that there are some such for which
pre-processing can make sense; generics is another good example.

I'm not saying pre-processing is *never* the right solution, just that you
can avoid the need for it in most of the situations you would have used it
if it was there :-)

> 2. Package name changes.  What a headache it was when swing went from
> com.sun.java.swing to java.awt.swing to javax.awt.swing.  I guess you could
> use reflection...

Or an abstraction layer; this is exactly where you want to use this kind of
technique if you want *runtime* portablility rather than just *compile time*
portability.

> 3. You can't use reflection to deal with classes that are referenced in a
> method's signature but is unavailable on the target platform.  For example,
> say you wanted to wrap ResultSet with a facade for some reason.  As of
> JDK1.4.x, java.sql.ResultSet now contains the method java.sql.Ref getRef(int
> column).  But java.sql.Ref doesn't exist in JDK1.1.  Reflection can't create
> classes that don't exist so your only alternatives to preprocessing are to
> create a version of the facade for each target platform (and have your build
> system play a shuffle game with the files) or to live with just a subset of
> the supported functionality.

Or to figure out a way of implementing the new functionality in terms of the
old and provide two concrete implementations behind the scenes. If you can
do the equivalent of

#if JDK14
...
#else
...

Then you can hide that decision making behind an abstraction so client code
needn't be aware of it, and you can make your choice at runtime.

> 4. Another issue similar to 3 is if a method's return type is changed.
> Again, reflection can't allow you to declare a facade with two methods
> identical except for their return types.  You can either change the method
> signatures in the facade (then use reflection) or create two different
> facades that you then have to have your build system swap around based on
> the target platform.

Or you can have an interface whose implementations take care of type
conversion when necessary. Reflection is the mechanism that lets you use
method signatures that were not there at compile time but are at runtime;
interfaces are the mechanism that allows you to make this transparent to
calling code.
 
> I would love to hear about any other uses for preprocessing for solving
> build problems!

My favourite would be to allow me to use generics in my code but still
support compiling on current JDKs. Like I said, there are valid uses for
pre-processing, it's just not as necessary as people who are used to having
it tend to think ;-)

L.

> 
> didge
> 
> -----Original Message-----
> From: Laurie Harper [mailto:zodiac@holoweb.net]
> Sent: Sunday, September 08, 2002 10:55 PM
> To: Ant Developers List
> Subject: Re: preprocessing java source
> 
> 
> Re-read your list of downsides; these are all good reasons why Java doesn't
> include a pre-processor.
> 
> Have you considered factoring out the JDK version dependent code and using
> reflection to select the appropriate implementation at run-time? That way,
> you eliminate the need for pre-processing to comile on any platform and, as
> a bonus, you get to run on any platform -- not just the one you compiled on.
> 
> That's the way to deal with platform dependencies in an object oriented
> way...
> 
> L.
> 
> On 9/6/02 8:05 PM, "didge" <di...@foundrylogic.com> wrote:
> 
>> Folks,
>> 
>> For some time I've been looking around for a solution to the problem of
>> handling multiple JDK platforms.  In C/C++, I would have relied on the
>> preprocessor and it would be a done deal.  No such luck in Java.
>> 
>> However, I realized that by combining Ant and Velocity, I could come up
> with
>> a workaround that might meet my needs.  I'm offering my solution to the
> this
>> group for possible inclusion as an optional ant task, if you feel that it
> is
>> generally useful as I think that it is.
>> 
>> Essentially, I subclassed the Javac ant task, and used Velocity to
>> preprocess each file as if they were template files. Then, I let Javac
>> continue to compile just the preprocessed files.
>> 
>> The net effect is that source files become Velocity templates and you can
>> now use VTL to do as you please with your source.  And, as an added bonus,
>> the VTL in the source files can access all of the properties known by Ant.
>> I simply put the Project.properties into the VelocityContext supplied to
> the
>> merge().  For example, you can get access to an ant property called foo by
>> using this VTL:
>> #if ( $ant.get("jdk.target").startsWith("1.4") )
>> some code...
>> #else ( $ant.get("jdk.target").startsWith("1.3") )
>> other code
>> #else
>> default code
>> #end
>> 
>> It is also possible to comment out the preprocessor directives so that
> IDEs
>> won't choke on the VTL directives.  Since Velocity ignores Java comments,
>> Velocity will process its directives regardless, but this can result in
> some
>> rather wierd situations for IDEs, as you can imagine, since you could end
> up
>> with a strange mix of code.  Non realtime parsing IDEs probably won't care
>> but those that do, such as IDEA, will markup the code with lots of syntax
>> errors.  But this should work very well at least for assert like
> statements.
>> 
>> On the bummer side, you must use ant to build effectively.  This is
>> mitigated a bit by the fact that most IDEs now let you execute your ant
>> targets directly, but you have to be smart about debugging.  You can no
>> longer debug against the original source, you now have to go against the
>> source in the temporary directory, unless your java-commented VTL is very
>> carefully written.
>> 
>> An additional bummer is for javadoc comments that reference set methods.
>> They usually look like someclass#setFoo, which Velocity interprets as a
>> directive.  But by simply escaping the #, e.g. someclass\#setFoo, all will
>> be well.
>> 
>> 
>> I've successfully used this now to write a complete java.sql wrapper
> library
>> that compiles from version 1.1.x through 1.4.x from a single source.
>> 
>> I'd be interested to know whay you think, if any of you have any time to
>> look at it.  See the attached .zip.
>> 
>> To build, you'll need to copy ant.jar (1.4.1 is what I used) and
>> velocity-dep-1.3.1-rc2.jar.  (I didn't include them with the zip because
>> they're large and you may not want to bother).  Unzip the .zip and copy
> the
>> aforementioned .jars into the lib directory and exectue 'ant build-g'.  To
>> see an example, execute 'ant ppjavac' which will preprocess and compile a
>> file under the example directory called PPTest.java.  You'll get a
>> sub-directory called 'foo' where the processed file will end up.  It's
>> .class will go into build/classes.
>> 
>> The interesting ant target in the build.xml is called ppjavac.  It takes
> all
>> of javac's attributes and adds two more.  preprocess allows you to turn on
>> or off preprocessing.  prepDir is the location of the temporary directory.
>> 
>> enjoy!
>> 
>> didge
>> 
>> 
>> --
>> To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
>> For additional commands, e-mail: <ma...@jakarta.apache.org>
> 
> 
> --
> To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
> For additional commands, e-mail: <ma...@jakarta.apache.org>
> 
> 
> 
> --
> To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
> For additional commands, e-mail: <ma...@jakarta.apache.org>
> 


--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>