001    /*
002     * Copyright 2004 Niclas Hedhman
003     * Copyright 2004-2005 Stephen McConnell
004     *
005     * Licensed  under the  Apache License,  Version 2.0  (the "License");
006     * you may not use  this file  except in  compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *   http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed  under the  License is distributed on an "AS IS" BASIS,
013     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
014     * implied.
015     *
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    package net.dpml.tools.tasks;
021    
022    import java.io.File;
023    import java.io.FileFilter;
024    import java.io.FileInputStream;
025    import java.io.IOException;
026    import java.text.SimpleDateFormat;
027    import java.util.Calendar;
028    import java.util.Hashtable;
029    import java.util.Iterator;
030    import java.util.Map;
031    import java.util.Date;
032    import java.util.regex.Matcher;
033    import java.util.regex.Pattern;
034    
035    import javax.xml.transform.Transformer;
036    import javax.xml.transform.TransformerFactory;
037    import javax.xml.transform.stream.StreamResult;
038    import javax.xml.transform.stream.StreamSource;
039    
040    import net.dpml.transit.Transit;
041    
042    import org.apache.tools.ant.BuildException;
043    import org.apache.tools.ant.Project;
044    import org.apache.tools.ant.taskdefs.Copy;
045    import org.apache.tools.ant.types.FileSet;
046    
047    /**
048     * Site generation.
049     *
050     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
051     * @version 1.2.0
052     */
053    public class DocTask extends GenericTask
054    {
055        private static final String ORG_NAME_VALUE = "The Digital Product Meta Library";
056        private static final String DOC_TEMP_VALUE = "docs";
057        private static final String DOC_SRC_VALUE = "docs";
058        private static final String DOC_RESOURCES_VALUE = "resources";
059        private static final String DOC_THEME_VALUE = "formal";
060        private static final String DOC_FORMAT_VALUE = "html";
061        private static final String DOC_DATE_FORMAT_VALUE = "yyyy-MMM-dd";
062        private static final String DOC_STYLE_VALUE = "standard";
063        private static final String DOC_ENTRY_VALUE = "";
064        private static final String DOC_LOGO_RIGHT_FILE_VALUE = "";
065        private static final String DOC_LOGO_RIGHT_URL_VALUE = "";
066        private static final String DOC_LOGO_LEFT_FILE_VALUE = "";
067        private static final String DOC_LOGO_LEFT_URL_VALUE = "";
068        private static final String DOC_LOGO_MIDDLE_FILE_VALUE = "";
069        private static final String DOC_LOGO_MIDDLE_URL_VALUE = "";
070        private static final String DOC_BRAND_NAME_VALUE = "DPML";
071        private static final String DOC_HOME_PATH_VALUE = "index.html";
072    
073       /**
074        * Constant organization key.
075        */
076        public static final String ORG_NAME_KEY = "project.organization.name";
077    
078       /**
079        * Constant temp docs key.
080        */
081        public static final String DOC_TEMP_KEY = "project.target.temp.docs";
082    
083       /**
084        * Constant docs src key.
085        */
086        public static final String DOC_SRC_KEY = "project.docs.src";
087    
088       /**
089        * Constant docs resources key.
090        */
091        public static final String DOC_RESOURCES_KEY = "project.docs.resources";
092    
093       /**
094        * Constant docs theme key.
095        */
096        public static final String DOC_THEME_KEY = "project.docs.theme";
097    
098       /**
099        * Constant docs output format key.
100        */
101        public static final String DOC_FORMAT_KEY = "project.docs.output.format";
102    
103       /**
104        * Constant docs date format key.
105        */
106        public static final String DOC_DATE_FORMAT_KEY = "project.docs.date.format";
107    
108       /**
109        * Constant docs output style key.
110        */
111        public static final String DOC_STYLE_KEY = "project.docs.output.style";
112    
113       /**
114        * Constant docs entry-point key.
115        */
116        public static final String DOC_ENTRY_KEY = "project.docs.entry-point";
117    
118       /**
119        * Constant docs logo-right-file key.
120        */
121        public static final String DOC_LOGO_RIGHT_FILE_KEY = "project.docs.logo.right.file";
122    
123       /**
124        * Constant docs logo-right url key.
125        */
126        public static final String DOC_LOGO_RIGHT_URL_KEY = "project.docs.logo.right.url";
127    
128       /**
129        * Constant docs logo-left file key.
130        */
131        public static final String DOC_LOGO_LEFT_FILE_KEY = "project.docs.logo.left.file";
132    
133       /**
134        * Constant docs logo-left url key.
135        */
136        public static final String DOC_LOGO_LEFT_URL_KEY = "project.docs.logo.left.url";
137    
138       /**
139        * Constant docs logo-middle file key.
140        */
141        public static final String DOC_LOGO_MIDDLE_FILE_KEY = "project.docs.logo.middle.file";
142    
143       /**
144        * Constant docs logo-middle url key.
145        */
146        public static final String DOC_LOGO_MIDDLE_URL_KEY = "project.docs.logo.middle.url";
147    
148       /**
149        * Constant docs brand key.
150        */
151        public static final String DOC_BRAND_NAME_KEY = "project.docs.brand.name";
152    
153       /**
154        * Constant docs anchor url key.
155        */
156        public static final String DOC_ANCHOR_URL_KEY = "project.docs.anchor.url";
157    
158       /**
159        * Constant docs home page path.
160        */
161        public static final String DOC_HOME_PATH_KEY = "project.docs.home.path";
162    
163        private String m_theme;
164        private File m_baseToDir;
165        private File m_baseSrcDir;
166        private File m_dest;
167    
168       /**
169        * Return the assigned theme.
170        * @return the theme
171        */
172        public String getTheme()
173        {
174            if( m_theme != null )
175            {
176                return m_theme;
177            }
178            String theme = getProject().getProperty( DOC_THEME_KEY );
179            if( null != theme )
180            {
181                return theme;
182            }
183            else
184            {
185                return getResource().getProperty( DOC_THEME_KEY, "formal" );
186            }
187        }
188        
189       /**
190        * Set a directory for ultimate replication of the generated documentation.
191        * @param dir the utilimate destination directory
192        */
193        public void setDest( File dir )
194        {
195            m_dest = dir;
196        }
197    
198       /**
199        * Set the doc theme.
200        * @param theme the theme name
201        */
202        public void setTheme( final String theme )
203        {
204            m_theme = theme;
205        }
206    
207       /**
208        * Initialize the task.
209        * @exception BuildException if a build error occurs
210        */
211        public void init() throws BuildException
212        {
213            if( !isInitialized() )
214            {
215                super.init();
216                final Project project = getProject();
217                project.setNewProperty( ORG_NAME_KEY, ORG_NAME_VALUE );
218                project.setNewProperty( DOC_SRC_KEY, DOC_SRC_VALUE );
219                project.setNewProperty( DOC_RESOURCES_KEY, DOC_RESOURCES_VALUE );
220                project.setNewProperty( DOC_FORMAT_KEY, DOC_FORMAT_VALUE );
221                project.setNewProperty( DOC_DATE_FORMAT_KEY, DOC_DATE_FORMAT_VALUE );
222                project.setNewProperty( DOC_STYLE_KEY, DOC_STYLE_VALUE );
223                project.setNewProperty( DOC_ENTRY_KEY, DOC_ENTRY_VALUE );
224                project.setNewProperty( DOC_TEMP_KEY, DOC_TEMP_VALUE );
225                project.setNewProperty( DOC_LOGO_RIGHT_FILE_KEY, DOC_LOGO_RIGHT_FILE_VALUE );
226                project.setNewProperty( DOC_LOGO_RIGHT_URL_KEY, DOC_LOGO_RIGHT_URL_VALUE );
227                project.setNewProperty( DOC_LOGO_LEFT_FILE_KEY, DOC_LOGO_LEFT_FILE_VALUE );
228                project.setNewProperty( DOC_LOGO_LEFT_URL_KEY, DOC_LOGO_LEFT_URL_VALUE );
229                project.setNewProperty( DOC_LOGO_MIDDLE_FILE_KEY, DOC_LOGO_MIDDLE_FILE_VALUE );
230                project.setNewProperty( DOC_LOGO_MIDDLE_URL_KEY, DOC_LOGO_MIDDLE_URL_VALUE );
231                project.setNewProperty( DOC_BRAND_NAME_KEY, DOC_BRAND_NAME_VALUE );
232            }
233        }
234    
235       /**
236        * Execute the task.
237        */
238        public void execute()
239        {
240            final Project project = getProject();
241            final File srcDir = getContext().getTargetBuildDocsDirectory();
242            if( !srcDir.exists() )
243            {
244                return;
245            }
246            log( "Filtered source: " + srcDir.getAbsolutePath() );
247    
248            //
249            // create the temporary directory into which we generate the
250            // navigation structure (normally target/temp/docs)
251            //
252    
253            final File temp = getContext().getTargetTempDirectory();
254            final File destDir = new File( temp, "docs" );
255            mkDir( destDir );
256    
257            //
258            // get the theme, output formats, etc.
259            //
260    
261            final File docs = getContext().getTargetDocsDirectory();
262            log( "Destination: " + docs.getAbsolutePath() );
263            mkDir( docs );
264            final String theme = getTheme();
265            final String output = getOutputFormat();
266            final String home = getHomePath();
267            final File themeRoot = getThemesDirectory();
268            final File themeDir = new File( themeRoot, theme + "/" + output );
269            
270            final File target = getContext().getTargetDirectory();
271            final String resourcesPath = project.getProperty( DOC_RESOURCES_KEY );
272            final File resources = new File( target, resourcesPath );
273    
274            log( "Year: " + getYear() );
275            log( "Theme: " + themeDir );
276    
277            //
278            // initiate the transformation starting with the generation of
279            // the navigation structure based on the src directory content
280            // into the temporary destingation directory, copy the content
281            // sources to to the temp directory, transform the content and
282            // generated navigation in the temp dir using the selected them
283            // into the final docs directory, and copy over resources to
284            // the final docs directory
285            //
286    
287            try
288            {
289                transformNavigation( themeDir, srcDir, destDir );
290                copySources( srcDir, destDir );
291                transformDocs( themeDir, destDir, docs );
292                copyThemeResources( themeDir, docs );
293                copySrcResources( resources, docs );
294            }
295            catch( BuildException e )
296            {
297                throw e;
298            }
299            catch( Throwable e )
300            {
301                log( "XSLT execution failed: " + e.getMessage() );
302                throw new BuildException( e );
303            }
304            
305            if( null != m_dest )
306            {
307                copy( docs, m_dest, "**/*", "" );
308            }
309        }
310        
311        private File getThemesDirectory()
312        {
313            return new File( Transit.DPML_PREFS, "dpml/tools/themes" );
314        }
315    
316        private String getHomePath()
317        {
318            return getProject().getProperty( DOC_HOME_PATH_KEY );
319        }
320    
321        private String getOutputFormat()
322        {
323            return getProject().getProperty( DOC_FORMAT_KEY );
324        }
325    
326        private String getOutputStyle()
327        {
328            return getProject().getProperty( DOC_STYLE_KEY );
329        }
330    
331        private String getDateFormat()
332        {
333            return getProject().getProperty( DOC_DATE_FORMAT_KEY );
334        }
335    
336        private void transformNavigation( final File themeDir, final File source, final File dest )
337        {
338            final File xslFile = new File( themeDir,  "nav-aggregate.xsl" );
339            if( !xslFile.exists() )
340            {
341                return;  // Theme may not use navigation.
342            }
343            log( "Transforming navigation." );
344            try
345            {
346                transformTrax(
347                    source, dest, xslFile,
348                    "^.*/navigation.xml$", "", ".xml" );
349            }
350            catch( BuildException e )
351            {
352                throw e;
353            }
354            catch( Throwable e )
355            {
356                final String error = 
357                  "Transformation failure: " + source;
358                throw new BuildException( error, e );
359            }
360        }
361    
362        private void copySources( final File source, final File dest )
363        {
364            copy( source, dest, "**/*", "**/navigation.xml" );
365        }
366    
367        private void transformDocs( final File themeDir, final File build, final File docs )
368        {
369            String style = getOutputStyle();
370            File xslFile = new File( themeDir,  style + ".xsl" );
371            String output = getOutputFormat();
372            log( "Transforming content." );
373            transformTrax(
374              build, docs, xslFile,
375              "^.*\\.xml$", "^.*/navigation.xml$", "." + output );
376        }
377    
378        private void copySrcResources( final File resources, final File docs )
379        {
380            copy( resources, docs, "**/*", "" );
381        }
382    
383        private void copyThemeResources( final File themeDir, final File docs )
384        {
385            final File fromDir = new File( themeDir, "resources" );
386            copy( fromDir, docs, "**/*", "" );
387        }
388    
389        private void copy( final File fromDir, final File toDir, final String includes, final String excludes )
390        {
391            if( !fromDir.exists() )
392            {
393                return;
394            }
395    
396            final FileSet from = new FileSet();
397            from.setDir( fromDir );
398            from.setIncludes( includes );
399            from.setExcludes( excludes );
400    
401            mkDir( toDir );
402    
403            final Copy copy = (Copy) getProject().createTask( "copy" );
404            copy.setTodir( toDir );
405            copy.addFileset( from );
406            copy.setPreserveLastModified( true );
407            copy.execute();
408        }
409    
410    
411        private void transformTrax(
412                final File srcDir, final File toDir, final File xslFile,
413                final String includes, final String excludes, final String extension )
414            throws BuildException
415        {
416            FileInputStream fis = null;
417            try
418            {
419                ClassLoader cl = getClass().getClassLoader();
420                Thread.currentThread().setContextClassLoader( cl );
421                StreamSource source = new StreamSource( xslFile );
422                final TransformerFactory tfactory = TransformerFactory.newInstance();
423                final Transformer transformer = tfactory.newTransformer( source );
424                final RegexpFilter filter = new RegexpFilter( includes, excludes );
425    
426                m_baseToDir = toDir;
427                m_baseSrcDir = srcDir.getAbsoluteFile();
428                String entrypoint = getEntryPoint();
429                if( "".equals( entrypoint ) )
430                {
431                    transform( transformer, m_baseSrcDir, toDir, filter, extension );
432                }
433                else
434                {
435                    File fileToConvert = new File( m_baseSrcDir, entrypoint );
436                    transformFile(
437                      transformer, fileToConvert, m_baseSrcDir, entrypoint, extension, m_baseToDir );
438                }
439            }
440            catch( BuildException e )
441            {
442                throw e;
443            }
444            catch( Exception e )
445            {
446                throw new BuildException( e.getMessage(), e );
447            }
448            finally
449            {
450                if( fis != null )
451                {
452                    try
453                    {
454                        fis.close();
455                    } 
456                    catch( IOException f )
457                    {
458                        log( f.toString() );
459                    }
460                }
461            }
462        }
463    
464        private void transform( final Transformer transformer, final File srcDir, final File toDir,
465            final FileFilter filter, final String extension )
466            throws BuildException
467        {
468            boolean recursive = isRecursive();
469            final File[] content = srcDir.listFiles( filter );
470            for( int i=0; i < content.length; i++ )
471            {
472                String base = content[i].getName();
473                if( content[i].isDirectory() && recursive )
474                {
475                    final File newDest = new File( toDir, base );
476                    newDest.mkdirs();
477                    transform( transformer, content[i], newDest, filter, extension );
478                }
479                if( content[i].isFile() )
480                {
481                    transformFile( transformer, content[i], srcDir, base, extension, toDir );
482                }
483            }
484        }
485    
486        private void transformFile( Transformer transformer, File content, File srcDir,
487                                    String base, String extension, File toDir )
488        {
489            String userDir = System.getProperty( "user.dir" );
490            System.setProperty( "user.dir", toDir.getAbsolutePath() );
491            final String year = getYear();
492            final String org = getOrganization();
493            final String copyright =
494              "Copyright " 
495              + year 
496              + ", " 
497              + org 
498              + " All rights reserved.";
499    
500            final String svnRoot = getProject().getProperty( DOC_ANCHOR_URL_KEY );
501            final String svnSource = svnRoot + getRelSrcPath( srcDir ) + "/" + base;
502    
503            final int pos = base.lastIndexOf( '.' );
504            if( pos > 0 )
505            {
506                base = base.substring( 0, pos );
507            }
508            base = base + extension;
509    
510            final File newDest = new File( toDir, base );
511            final StreamSource xml = new StreamSource( content );
512            final StreamResult out = new StreamResult( newDest );
513    
514            transformer.clearParameters();
515            transformer.setParameter( "directory", getRelToPath( toDir ) );
516            transformer.setParameter( "fullpath", getRelToPath( newDest ) );
517            transformer.setParameter( "file", base );
518            transformer.setParameter( "svn-location", svnSource );
519            transformer.setParameter( "copyright", copyright );
520            transformer.setParameter(
521                "logoright_file",
522                getProject().getProperty( DOC_LOGO_RIGHT_FILE_KEY ).trim() );
523            transformer.setParameter(
524                "logoright_url",
525                getProject().getProperty( DOC_LOGO_RIGHT_URL_KEY ).trim() );
526            transformer.setParameter(
527                "logoleft_file",
528                getProject().getProperty( DOC_LOGO_LEFT_FILE_KEY ).trim() );
529            transformer.setParameter(
530                "logoleft_url",
531                getProject().getProperty( DOC_LOGO_LEFT_URL_KEY ).trim() );
532            transformer.setParameter(
533                "logomiddle_file",
534                getProject().getProperty( DOC_LOGO_MIDDLE_FILE_KEY ).trim() );
535            transformer.setParameter(
536                "logomiddle_url",
537                getProject().getProperty( DOC_LOGO_MIDDLE_URL_KEY ).trim() );
538            transformer.setParameter(
539                "brand_name",
540                getProject().getProperty( DOC_BRAND_NAME_KEY ).trim() );
541            transformer.setParameter( "generated_date", getNow() );
542            setOtherProperties( transformer );
543            try
544            {
545                transformer.transform( xml, out );
546            }
547            catch( BuildException e )
548            {
549                throw e;
550            }
551            catch( Throwable e )
552            {
553                final String error = 
554                  "An error occured while attempting to transform document: "
555                  + getRelToPath( newDest );
556                throw new BuildException( error, e, getLocation() );
557            }
558            finally
559            {
560                System.setProperty( "user.dir", userDir );
561            }
562        }
563    
564        private String getRelToPath( final File dir )
565        {
566            final String basedir = m_baseToDir.getAbsolutePath();
567            final String curdir = dir.getAbsolutePath();
568            return curdir.substring( basedir.length() );
569        }
570    
571        private String getRelSrcPath( final File dir )
572        {
573            final String basedir = m_baseSrcDir.getAbsolutePath();
574            final String curdir = dir.getAbsolutePath();
575            return curdir.substring( basedir.length() );
576        }
577    
578       /**
579        * Utility regualar expression filter.
580        */
581        public class RegexpFilter implements FileFilter
582        {
583            private Pattern m_includes;
584            private Pattern m_excludes;
585    
586           /**
587            * New filter creation.
588            * @param includes the includes
589            * @param excludes the excludes
590            */
591            public RegexpFilter( final String includes, final String excludes )
592            {
593                m_includes = Pattern.compile( includes );
594                m_excludes = Pattern.compile( excludes );
595            }
596    
597           /**
598            * Test supplied file for acceptance.
599            * @param file the candidate
600            * @return TRUE if acceptable
601            */
602            public boolean accept( final File file )
603            {
604                final String basename = file.getName();
605    
606                if( basename.equals( ".svn" ) )
607                {
608                    return false;
609                }
610    
611                if( basename.equals( "CVS" ) )
612                {
613                    return false;
614                }
615    
616                if( file.isDirectory() )
617                {
618                    return true;
619                }
620    
621                final String fullpath = file.getAbsolutePath().replace( '\\', '/' );
622    
623                Matcher m = m_includes.matcher( fullpath );
624                if( !m.matches() )
625                {
626                    return false;
627                }
628    
629                m = m_excludes.matcher( fullpath );
630                return !m.matches();
631            }
632        }
633    
634        private String getYear()
635        {
636            String year = getProject().getProperty( "magic.year" );
637            if( year != null )
638            {
639                return year;
640            }
641            else
642            {
643                Calendar cal = Calendar.getInstance();
644                return Integer.toString( cal.get( Calendar.YEAR ) );
645            }
646        }
647    
648        private String getOrganization()
649        {
650            return getProject().getProperty( ORG_NAME_KEY );
651        }
652    
653        private boolean isRecursive()
654        {
655            return "".equals( getEntryPoint() );
656        }
657    
658        private String getEntryPoint()
659        {
660            String point = getProject().getProperty( DOC_ENTRY_KEY );
661            if( point == null )
662            {
663                return "";
664            }
665            return point;
666        }
667    
668        private void setOtherProperties( Transformer transformer )
669        {
670            String prefix = "project.docs.xsl.";
671            int prefixLen = prefix.length();
672    
673            Hashtable p = getProject().getProperties();
674            Iterator list = p.entrySet().iterator();
675            while( list.hasNext() )
676            {
677                Map.Entry entry = (Map.Entry) list.next();
678                String key = (String) entry.getKey();
679                if( key.startsWith( prefix ) )
680                {
681                    String value = (String) entry.getValue();
682                    key = key.substring( prefixLen );
683                    transformer.setParameter( key, value );
684                    log( "Setting " + key + "=" + value );
685                }
686            }
687        }
688    
689        private String getNow()
690        {
691            Date now = new Date();
692            SimpleDateFormat sdf = new SimpleDateFormat( getDateFormat() );
693            String result = sdf.format( now );
694            return result;
695        }
696    }