package Template::Plugin::Java; ($VERSION) = '$ProjectVersion: 0.4 $' =~ /\$ProjectVersion:\s+(\S+)/; =head1 NAME Template::Plugin::Java - Generate Java Classes from XML description files and templates. =head1 SYNOPSIS From an xml file such as: value2 10 String 20 Through the program "tjava": tjava [options] [file.xml ...] Via a template, such as: [% USE Java %] package $package; public class $class { [% FOREACH Java.variables %] $type $name = $initializer; [% END %] //... etc } To generated Java source code in the appropriate directory as determined by the package of the .xml file's directory, specified package option if any, and CLASSPATH. =head1 OPTIONS Any options may be given besides those listed, these are passed directly to the Templates being processed in the stash (the variable table at time of processing). They can be given in the ... section of an XML file (in which case, don't use the -- dashes) as attributes or elements, or on the command line. =over 8 =item B<--template> Name of the template to process. No extension is assumed by default unlike in the previous version. =item B<--package> Destination package to put the generated classes, otherwise will be determined from how the current directory relates to the CLASSPATH. =item B<--class> Class name to use, otherwise will infer from the root tag of the XML file. =item B<--templatePath> Colon separated path where the templates can be found, overrides the environment variable TEMPLATEPATH. This doesn't work right now, so use the TEMPLATEPATH environment variable. =item B<--genContainers> If set to 0, classes for subcontainers will not be generated. This is generally not useful. =item B<--containerTemplate> By default set to F, this is the default template, as well as the template used for sub-containers. =item B<--containerNamePrefix> By default, if generating class Foo that needs to have a sub container wrapped in tag , it's name will be FooBar. This is safe and won't cause collisions with different classes having sub containers of the same name (until some sort of dependency checking code is introduced). To turn this off, set it to the empty string "". =item B<--interface> Interface to add to list of implemented interfaces, can be supplied multiple times. Make sure you append any necessary code to implement any of these interfaces. =item B<--append> Text to insert in the generated class body. =item B<--appendFile> Will insert text read from the file specified into the generated class body. This option and the B<--append> option are mutually exclusive. =item B<--file[s]> The XML file(s) to parse. This is useful for when the Plugin is instantiated from a custom script, not via tjava or inside a template. Any other option will be placed into the stash for the templates to use, making tjava very useful with your custom templates. Anything that's not an option will be assumed to be a file. =back =head1 DESCRIPTION Template::Plugin::Java is a plugin module for the Template toolkit that makes it easier to write templates for generating Java source code, ultimately for transforming XML descriptions into Java language sources. It can be used either directly on the command line, or loaded from a Template with a C<[% USE Java %]> statement, or in many other ways. It tries to be intelligent and figure out what context you are using it in. I'll write more eventually, for now see the examples in the distribution. =head1 METHODS =over 8 =cut require Template::Plugin; @ISA = 'Template::Plugin'; use strict; use Carp qw/verbose croak/; use Template::Plugin::Java::Utils qw( parseOptions findPackageDir isNum determinePackage createTemplate parseCmdLine javaTypeName ); use Template::Plugin::Java::Constants qw/:all/; =item B This, the constructor, does everything necessary to create a new instance of the Java plugin, based on context. If not given a context, takes control of the command line and then parses any options and files given. This is what the "tjava" utility does. =cut sub new { use XML::Simple; use File::Basename; my $class = shift; my $self = bless {}, ref $class || $class; my $context; my $params = {}; my $arg1 = $_[0]; if (@_ <= 1 && not ref $arg1) { $params->{file} = shift; } elsif (not ref $arg1) { $params = {@_}; } elsif (UNIVERSAL::isa($arg1, 'Template::Context')) { $self->context(shift); } elsif (UNIVERSAL::isa($arg1, 'HASH')) { $params = { %{+shift}, @_ }; } $self->context(delete $params->{context}); my $defaults = delete $params->{defaults} || {}; my $cmd_line = delete $params->{cmdLine} || {}; # Automatically parse the command line unless either explicitly told not to, or # a the object has been created inside a template as an actual plugin. unless ((exists $params->{parseCmdLine} && (not $params->{parseCmdLine})) || $self->context) { $cmd_line = { %$cmd_line, parseOptions( parseCmdLine ) }; # Use rest of @ARGV as files. push @{$params->{files}}, @ARGV; @ARGV = (); } unless ($self->context) { $self->template ( createTemplate delete $params->{templateOptions} ); } my $files = delete $params->{file} || delete $params->{files}; my @files; if (defined $files) { if (UNIVERSAL::isa($files, 'ARRAY')) { @files = @$files; } else { push @files, $files; } } # The ! eof STDIN is necessary here, because sub-templates will want to create # new instances of this Plugin, when the process still has a redirected STDIN, # just with no data to read. Using eof on a terminal is bad, but this doesn't # happen because of the && short circuit. if (scalar @files == 0 && ! -t STDIN && ! eof STDIN) { push @files, '-'; } for my $file_name (@files) { my $stash; if ($file_name ne '-') { # Prepend ./ if relative path. $file_name =~ s!^([^/-])!./$1!; $stash = XMLin ( $file_name, keyattr => "", keeproot => 1, cache => 'storable' ); } else { # Reading from STDIN. my $data; { local @ARGV = '-'; $data = join '', <>; } $stash = XMLin ( $data, keyattr => "", keeproot => 1, ); } my $root = (keys %$stash)[0]; $stash = {%{$stash->{$root}}}; my $context = delete $stash->{'java:'} || {}; $stash = { parseOptions( %$defaults, %$params, %$context, %$cmd_line ), variables => $stash }; $stash->{tag} = $root; $stash->{class} ||= ucfirst $root; # Allow nopackage="true" to create a class that isn't in a package. { # Turn off warnings about comparing uninitialized values. local $^W = undef; if (!$stash->{package} && $stash->{package} ne '0') { $stash->{package} = determinePackage dirname($file_name); } } $stash->{genContainers} ||= TRUE; $stash->{containerTemplate} ||= 'Container'; $stash->{template} ||= $stash->{containerTemplate}; $stash->{containerNamePrefix} = $stash->{class} if not exists $stash->{containerNamePrefix}; if (exists $stash->{appendFile}) { use IO::File; my $file = new IO::File $stash->{appendFile} or die "Could not open $stash->{appendFile}"; local $/ = undef; $stash->{append} .= <$file>; } $self->genClass($stash); } return $self; } =item B