001    /*
002     * Copyright 2001-2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.doclets.internal.toolkit.util;
027    
028    import java.io.*;
029    import java.util.*;
030    import javax.tools.FileObject;
031    
032    import com.sun.javadoc.*;
033    import com.sun.tools.doclets.internal.toolkit.*;
034    
035    /**
036     * Converts Java Source Code to HTML.
037     *
038     * This code is not part of an API.
039     * It is implementation that is subject to change.
040     * Do not use it as an API
041     *
042     * @author Jamie Ho
043     * @since 1.4
044     */
045    public class SourceToHTMLConverter {
046    
047        /**
048         * The background color.
049         */
050        protected static final String BGCOLOR = "white";
051    
052        /**
053         * The line number color.
054         */
055        protected static final String LINE_NO_COLOR = "green";
056    
057        /**
058         * The number of trailing blank lines at the end of the page.
059         * This is inserted so that anchors at the bottom of small pages
060         * can be reached.
061         */
062        protected static final int NUM_BLANK_LINES = 60;
063    
064    
065        /**
066         * Source is converted to HTML using static methods below.
067         */
068        private SourceToHTMLConverter() {}
069    
070        /**
071         * Convert the Classes in the given RootDoc to an HTML.
072         * @param configuration the configuration.
073         * @param rd the RootDoc to convert.
074         * @param outputdir the name of the directory to output to.
075         */
076        public static void convertRoot(Configuration configuration, RootDoc rd, String outputdir) {
077            if (rd == null || outputdir == null) {
078                return;
079            }
080            PackageDoc[] pds = rd.specifiedPackages();
081            for (int i = 0; i < pds.length; i++) {
082                convertPackage(configuration, pds[i], outputdir);
083            }
084            ClassDoc[] cds = rd.specifiedClasses();
085            for (int i = 0; i < cds.length; i++) {
086                convertClass(configuration, cds[i],
087                    getPackageOutputDir(outputdir, cds[i].containingPackage()));
088            }
089        }
090    
091        /**
092         * Convert the Classes in the given Package to an HTML.
093         * @param configuration the configuration.
094         * @param pd the Package to convert.
095         * @param outputdir the name of the directory to output to.
096         */
097        public static void convertPackage(Configuration configuration, PackageDoc pd, String outputdir) {
098            if (pd == null || outputdir == null) {
099                return;
100            }
101            String classOutputdir = getPackageOutputDir(outputdir, pd);
102            ClassDoc[] cds = pd.allClasses();
103            for (int i = 0; i < cds.length; i++) {
104                convertClass(configuration, cds[i], classOutputdir);
105            }
106        }
107    
108        /**
109         * Return the directory write output to for the given package.
110         * @param outputDir the directory to output to.
111         * @param pd the Package to generate output for.
112         */
113        private static String getPackageOutputDir(String outputDir, PackageDoc pd) {
114            return outputDir + File.separator +
115                DirectoryManager.getDirectoryPath(pd) + File.separator;
116        }
117    
118        /**
119         * Convert the given Class to an HTML.
120         * @param configuration the configuration.
121         * @param cd the class to convert.
122         * @param outputdir the name of the directory to output to.
123         */
124        public static void convertClass(Configuration configuration, ClassDoc cd, String outputdir) {
125            if (cd == null || outputdir == null) {
126                return;
127            }
128            try {
129                SourcePosition sp = cd.position();
130                if (sp == null)
131                    return;
132                Reader r;
133                // temp hack until we can update SourcePosition API.
134                if (sp instanceof com.sun.tools.javadoc.SourcePositionImpl) {
135                    FileObject fo = ((com.sun.tools.javadoc.SourcePositionImpl) sp).fileObject();
136                    if (fo == null)
137                        return;
138                    r = fo.openReader(true);
139                } else {
140                    File file = sp.file();
141                    if (file == null)
142                        return;
143                    r = new FileReader(file);
144                }
145                LineNumberReader reader = new LineNumberReader(r);
146                int lineno = 1;
147                String line;
148                StringBuffer output = new StringBuffer();
149                try {
150                    while ((line = reader.readLine()) != null) {
151                        output.append(formatLine(line, configuration.sourcetab, lineno));
152                        lineno++;
153                    }
154                } finally {
155                    reader.close();
156                }
157                output = addLineNumbers(output.toString());
158                output.insert(0, getHeader(configuration));
159                output.append(getFooter());
160                writeToFile(output.toString(), outputdir, cd.name(), configuration);
161            } catch (Exception e){
162                e.printStackTrace();
163            }
164        }
165    
166        /**
167         * Write the output to the file.
168         * @param output the string to output.
169         * @param outputDir the directory to output to.
170         * @param className the name of the class that I am converting to HTML.
171         * @param configuration the Doclet configuration to pass notices to.
172         */
173        private static void writeToFile(String output, String outputDir, String className, Configuration configuration) throws IOException {
174            File dir = new File(outputDir);
175            dir.mkdirs();
176            File newFile = new File(dir, className + ".html");
177            configuration.message.notice("doclet.Generating_0", newFile.getPath());
178            FileOutputStream fout = new FileOutputStream(newFile);
179            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fout));
180            bw.write(output);
181            bw.close();
182            fout.close();
183        }
184    
185        /**
186         * Given a <code>String</code>, add line numbers.
187         * @param s the text to add line numbers to.
188         *
189         * @return the string buffer with the line numbering for each line.
190         */
191        private static StringBuffer addLineNumbers(String s) {
192            StringBuffer sb = new StringBuffer();
193            StringTokenizer st = new StringTokenizer(s, "\n", true);
194            int lineno = 1;
195            String current;
196            while(st.hasMoreTokens()){
197                current = st.nextToken();
198                sb.append(current.equals("\n") ?
199                        getHTMLLineNo(lineno) + current :
200                        getHTMLLineNo(lineno) + current + st.nextToken());
201                lineno++;
202            }
203            return sb;
204        }
205    
206        /**
207         * Get the header.
208         * @param configuration the Doclet configuration
209         * @return the header to the output file
210         */
211        protected static String getHeader(Configuration configuration) {
212            StringBuffer result = new StringBuffer("<HTML lang=\"" + configuration.getLocale().getLanguage() + "\">" + DocletConstants.NL);
213            result.append("<BODY BGCOLOR=\""+ BGCOLOR + "\">" + DocletConstants.NL);
214            result.append("<PRE>" + DocletConstants.NL);
215            return result.toString();
216        }
217    
218        /**
219         * Get the footer
220         * @return the footer to the output file
221         */
222        protected static String getFooter() {
223            StringBuffer footer = new StringBuffer();
224            for (int i = 0; i < NUM_BLANK_LINES; i++) {
225                footer.append(DocletConstants.NL);
226            }
227            footer.append("</PRE>" + DocletConstants.NL + "</BODY>" +
228                DocletConstants.NL + "</HTML>" + DocletConstants.NL);
229            return footer.toString();
230        }
231    
232        /**
233         * Get the HTML for the lines.
234         * @param lineno The line number
235         * @return the HTML code for the line
236         */
237        protected static String getHTMLLineNo(int lineno) {
238            StringBuffer result = new StringBuffer("<FONT color=\"" + LINE_NO_COLOR
239                + "\">");
240            if (lineno < 10) {
241                result.append("00" + ((new Integer(lineno)).toString()));
242            } else if (lineno < 100) {
243                result.append("0" + ((new Integer(lineno)).toString()));
244            } else {
245                result.append((new Integer(lineno)).toString());
246            }
247            result.append("</FONT>    ");
248            return result.toString();
249        }
250    
251        /**
252         * Format a given line of source. <br>
253         * Note:  In the future, we will add special colors for constructs in the
254         * language.
255         * @param line the string to format.
256         * @param tabLength the number of spaces for each tab.
257         * @param currentLineNo the current number.
258         */
259        protected static String formatLine(String line, int tabLength, int currentLineNo) {
260            if (line == null) {
261                return null;
262            }
263            StringBuffer lineBuffer = new StringBuffer(Util.escapeHtmlChars(line));
264            //Insert an anchor for the line
265            lineBuffer.append("<a name=\"line." + Integer.toString(currentLineNo) + "\"></a>");
266            lineBuffer.append(DocletConstants.NL);
267            Util.replaceTabs(tabLength, lineBuffer);
268            return lineBuffer.toString();
269        }
270    
271        /**
272         * Given an array of <code>Doc</code>s, add to the given <code>HashMap</code> the
273         * line numbers and anchors that should be inserted in the output at those lines.
274         * @param docs the array of <code>Doc</code>s to add anchors for.
275         * @param hash the <code>HashMap</code> to add to.
276         */
277        protected static void addToHash(Doc[] docs, HashMap<Integer,String> hash) {
278            if(docs == null) {
279                return;
280            }
281            for(int i = 0; i < docs.length; i++) {
282                hash.put(docs[i].position().line(), getAnchor(docs[i]));
283            }
284        }
285    
286        /**
287         * Given a <code>Doc</code>, return an anchor for it.
288         * @param d the <code>Doc</code> to check.
289         * @return an anchor of the form &lt;a name="my_name">&lt;/a>
290         */
291        protected static String getAnchor(Doc d) {
292            return "    <a name=\"" + getAnchorName(d) + "\"></a>";
293        }
294    
295        /**
296         * Given a <code>Doc</code>, return an anchor name for it.
297         * @param d the <code>Doc</code> to check.
298         * @return the name of the anchor.
299         */
300        public static String getAnchorName(Doc d) {
301            return "line." + d.position().line();
302        }
303    }