001/* A parser for DocML (Doc Markup Language).
002
003 Copyright (c) 2006-2018 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.vergil.actor;
029
030import java.io.BufferedReader;
031import java.io.File;
032import java.io.IOException;
033import java.io.InputStream;
034import java.io.InputStreamReader;
035import java.io.Reader;
036import java.io.StringReader;
037import java.net.URL;
038import java.util.Date;
039import java.util.HashMap;
040import java.util.Iterator;
041import java.util.LinkedList;
042import java.util.List;
043import java.util.Stack;
044
045import com.microstar.xml.HandlerBase;
046import com.microstar.xml.XmlException;
047import com.microstar.xml.XmlParser;
048
049import ptolemy.actor.TypedCompositeActor;
050import ptolemy.actor.gui.Configuration;
051import ptolemy.data.expr.Parameter;
052import ptolemy.kernel.Entity;
053import ptolemy.kernel.Port;
054import ptolemy.kernel.util.Attribute;
055import ptolemy.kernel.util.Instantiable;
056import ptolemy.kernel.util.NamedObj;
057import ptolemy.kernel.util.Settable;
058import ptolemy.kernel.util.StringAttribute;
059import ptolemy.util.FileUtilities;
060import ptolemy.vergil.basic.DocAttribute;
061
062///////////////////////////////////////////////////////////////////
063//// DocManager
064
065/**
066 A manager for documentation for an associated Ptolemy II object.
067 The constructor specifies the associated Ptolemy II object, and
068 then various methods provide access to portions of the documentation.
069 For example, the getDescription() method returns a description of
070 the object. The getPortDoc() method returns a description of the
071 specified port.
072 <p>
073 The documentation is constructed by a multi-tiered method.
074 The first level of information is provided by an
075 attribute named DOC_ATTRIBUTE_NAME contained by the object.
076 The second level of information is provided by an XML file
077 in the same package as the class of the associated object.
078 The name of the XML file is "xDoc.xml", where x is the name
079 of the class of the object.
080 The third level of information is provided by an XML file
081 associated with the base class of the associated object.
082 The remaining levels are provided by searching up the
083 inheritance hierarchy.
084 When a method of the DocManager class is invoked to get
085 documentation information, this class looks first in the
086 first tier for the information. If the information is
087 not present in the first tier, then it looks in the second
088 tier, etc. If the information is not present in any tier,
089 then it returns a string indicating that there is no
090 information. Except for the first tier, the
091 documentation information is constructed
092 lazily, only when the methods to access the information
093 are invoked, and only if the first tier has not
094 provided the information.
095 <p>
096 If the information is found but is malformed, then
097 all information methods return a description of the error.
098 <p>
099 At all tiers, the documentation information is given in XML
100 with a specified DTD.
101 <p>
102 A doc file should be an XML file beginning with
103 <pre>
104 &lt;?xml version="1.0" standalone="yes"?&gt;
105 &lt;!DOCTYPE doc PUBLIC "-//UC Berkeley//DTD DocML 1//EN"
106 "http://ptolemy.eecs.berkeley.edu/xml/dtd/DocML_1.dtd"&gt;
107 </pre>
108 and should then have a top-level element of the form
109 <pre>
110 &lt;doc name="<i>actorName</i>" class="<i>actorClass</i>"&gt;
111 </pre>
112 The main description is text included within the description
113 element, as in
114 <pre>
115 &lt;description&gt;
116 <i>description here</i>
117 &lt;/description&gt;
118 </pre>
119 The description can include HTML formatting, although any
120 &lt; and &gt; should be escaped and represented as &amp;lt;
121 and &amp;gt;.
122 <p>
123 Additional information can be provided in the author, version,
124 since, Pt.ProposedRating, and Pt.AcceptedRating elements.
125 These are, like the description, simple text that gets rendered
126 (and HTML formatted) in the documentation.
127 <p>
128 Documentation for ports and parameters is given using the
129 following forms:
130 <pre>
131 &lt;port name="<i>portName</i>"
132 <i>documentation</i>
133 &lt;/port&gt;
134 &lt;property name="<i>parameterName</i>"
135 <i>documentation</i>
136 &lt;/property&gt;
137 </pre>
138 The use of the "property" keyword matches MoML.
139
140 @author Edward A. Lee
141 @version $Id$
142 @since Ptolemy II 5.2
143 @Pt.ProposedRating Red (eal)
144 @Pt.AcceptedRating Red (cxh)
145 */
146public class DocManager extends HandlerBase {
147
148    /** Construct a manager to handle documentation for the specified target.
149     *  @param configuration The configuration in which to look up the
150     *  _docApplicationSpecializer and _applicationName parameters
151     *  @param target The object to be documented.
152     */
153    public DocManager(Configuration configuration, NamedObj target) {
154        super();
155        _configuration = configuration;
156        _target = target;
157        _targetClass = target.getClass();
158        _className = target.getClassName();
159        //try {
160        List docAttributes = _target.attributeList(DocAttribute.class);
161        // Get the last doc attribute.
162        if (docAttributes.size() > 0) {
163            DocAttribute instanceDoc = (DocAttribute) docAttributes
164                    .get(docAttributes.size() - 1);
165            // Populate fields from the attribute.
166            //String descriptionValue = instanceDoc.description.stringValue();
167            String descriptionValue = instanceDoc.description.getExpression();
168            if (descriptionValue != null
169                    && !descriptionValue.trim().equals("")) {
170                _isInstanceDoc = true;
171                _description = descriptionValue;
172            }
173            /* No rating fields in instance documentation.
174             String acceptedRatingValue = instanceDoc.acceptedRating.getExpression();
175             if (!acceptedRatingValue.trim().equals("")) {
176             _isInstanceDoc = true;
177             _ptAcceptedRating = acceptedRatingValue;
178             }
179             */
180            String authorValue = instanceDoc.author.getExpression();
181            if (authorValue != null && !authorValue.trim().equals("")) {
182                _isInstanceDoc = true;
183                _author = authorValue;
184            }
185            /* No rating fields in instance documentation.
186             String proposedRatingValue = instanceDoc.proposedRating.getExpression();
187             if (!proposedRatingValue.trim().equals("")) {
188             _isInstanceDoc = true;
189             _ptProposedRating = proposedRatingValue;
190             }
191             */
192            String sinceValue = instanceDoc.since.getExpression();
193            if (sinceValue != null && !sinceValue.trim().equals("")) {
194                _isInstanceDoc = true;
195                _since = sinceValue;
196            }
197            String versionValue = instanceDoc.version.getExpression();
198            if (versionValue != null && !versionValue.trim().equals("")) {
199                _isInstanceDoc = true;
200                _version = versionValue;
201            }
202
203            // Next look for attributes.
204            Iterator attributes = target.attributeList(Settable.class)
205                    .iterator();
206            while (attributes.hasNext()) {
207                NamedObj attribute = (NamedObj) attributes.next();
208                if (((Settable) attribute).getVisibility() != Settable.NONE) {
209                    String attributeDoc = instanceDoc
210                            .getParameterDoc(attribute.getName());
211                    if (attributeDoc != null
212                            && !attributeDoc.trim().equals("")) {
213                        _isInstanceDoc = true;
214                        _properties.put(attribute.getName(), attributeDoc);
215                    }
216                }
217            }
218            // Next look for ports.
219            if (target instanceof Entity) {
220                Iterator ports = ((Entity) target).portList().iterator();
221                while (ports.hasNext()) {
222                    Port port = (Port) ports.next();
223                    String portDoc = instanceDoc.getPortDoc(port.getName());
224                    if (portDoc != null && !portDoc.trim().equals("")) {
225                        _isInstanceDoc = true;
226                        _ports.put(port.getName(), portDoc);
227                    }
228                }
229            }
230        }
231        //} catch (IllegalActionException e) {
232        //_exception = "Error evaluating DocAttribute parameter:\n" + e + ptolemy.kernel.util.KernelException.stackTraceToString(e);
233        //}
234    }
235
236    /** Construct a manager to handle documentation for the specified target
237     *  class.
238     *  @param configuration The configuration in which to look up the
239     *  _docApplicationSpecializer and _applicationName parameters
240     *  @param targetClass The class to be documented.
241     */
242    public DocManager(Configuration configuration, Class targetClass) {
243        super();
244        _configuration = configuration;
245        _targetClass = targetClass;
246        _className = _targetClass.getName();
247    }
248
249    /** Construct a manager for documentation at the specified URL.
250     *  @param configuration The configuration in which to look up the
251     *  _docApplicationSpecializer and _applicationName parameters
252     *  @param url The URL.
253     */
254    public DocManager(Configuration configuration, URL url) {
255        super();
256        _configuration = configuration;
257        try {
258            parse(null, url.openStream());
259            if (_className != null) {
260                _targetClass = Class.forName(_className);
261            } else {
262                System.err.println("DocManager: while reading " + url
263                        + ", _className was null?");
264            }
265        } catch (Exception ex) {
266            ex.printStackTrace();
267            _exception = "Error reading URL: " + url.toExternalForm()
268                    + "\n<pre>\n" + ex + "\n</pre>\n";
269        }
270        _docFileHasBeenRead = true;
271    }
272
273    ///////////////////////////////////////////////////////////////////
274    ////                         public methods                    ////
275
276    /** Handle an attribute assignment that is part of an XML element.
277     *  This method is called prior to the corresponding startElement()
278     *  call, so it simply accumulates attributes in a hashtable for
279     *  use by startElement().
280     *  @param name The name of the attribute.
281     *  @param value The value of the attribute, or null if the attribute
282     *   is <code>#IMPLIED</code> and not specified.
283     *  @param specified True if the value is specified, false if the
284     *   value comes from the default value in the DTD rather than from
285     *   the XML file.
286     *  @exception XmlException If the name or value is null.
287     */
288    @Override
289    public void attribute(String name, String value, boolean specified)
290            throws XmlException {
291        if (name == null) {
292            throw new XmlException("Attribute has no name",
293                    _currentExternalEntity(), _parser.getLineNumber(),
294                    _parser.getColumnNumber());
295        }
296
297        // NOTE: value may be null if attribute default is #IMPLIED.
298        if (value != null) {
299            _attributes.put(name, value);
300        }
301    }
302
303    /** Given a dot separated class name, return the URL of the
304     *  documentation.
305     *
306     *  <p>If the configuration has a parameter
307     *  _docApplicationSpecializer and that parameter names a class
308     *  that that implements the {@link DocApplicationSpecializer}
309     *  interface, then we pass the class name to
310     *  {@link DocApplicationSpecializer#docClassNameToURL(String, String, boolean, boolean, boolean, boolean)}
311     *  and if a non-null is returned from docClassNameToURL(), we
312     *  return that value.
313     *
314     *  <p>If the configuration has a parameter _applicationName, then
315     *  we search in <code>doc/codeDoc<i>applicationName</i></code> for
316     *  the PtDoc, Javadoc and Actor Index files.  Otherwise, we search
317     *  in <code>doc/codeDoc</code>.  Source files are searched for
318     *  in the classpath by using getResource().
319     *
320     *  <p>The <i>lookForPtDoc</i>, <i>lookForJavadoc</i>,
321     *  <i>lookForSource</i> and <i>lookForActorIndex</i> parameters
322     *  control which documents are searched for.  The documents are
323     *  searched in the same order as the parameters that have true
324     *  values, that is if the parameters are true, true, false,
325     *  false, then if the the PtDoc .xml file is searched for
326     *  locally, then the Javadoc .html file is search for locally and
327     *  then if the _remoteDocumentation base attribute is set the
328     *  PtDoc .xml file is searched for on the remote host and then
329     *  the Javadoc .html file is search for on the remote host.
330     *
331     *  @param configuration The configuration in which to look up the
332     *  _docApplicationSpecializer and _applicationName parameters
333     *  @param className The dot separated class name.
334     *  @param lookForPtDoc True if we should look for ptdoc .xml files.
335     *  @param lookForJavadoc True if we should look for javadoc files.
336     *  @param lookForSource True if we should look for source files.
337     *  Note that lookForPtDoc and lookForJavadoc must both be false for
338     *  the source code to be found.
339     *  @param lookForActorIndex True if we should look for the actor index.
340     *  @return The URL of the documentation, if any.  If no documentation
341     *  was found, return null.
342     */
343    public static URL docClassNameToURL(Configuration configuration,
344            String className, boolean lookForPtDoc, boolean lookForJavadoc,
345            boolean lookForSource, boolean lookForActorIndex) {
346        URL toRead = null;
347        try {
348            // If the configuration has a parameter _docApplicationSpecializer
349            // and that parameter names a class that that implements the
350            // DocApplicationSpecializer interface, then we call
351            // docClassNameToURL().
352
353            Parameter docApplicationSpecializerParameter = (Parameter) configuration
354                    .getAttribute("_docApplicationSpecializer",
355                            Parameter.class);
356            if (docApplicationSpecializerParameter != null) {
357                String docApplicationSpecializerClassName = docApplicationSpecializerParameter
358                        .getExpression();
359
360                try {
361                    Class docApplicationSpecializerClass = Class
362                            .forName(docApplicationSpecializerClassName);
363                    DocApplicationSpecializer docApplicationSpecializer = (DocApplicationSpecializer) docApplicationSpecializerClass
364                            .newInstance();
365                    toRead = docApplicationSpecializer.docClassNameToURL(
366                            _remoteDocumentationURLBase, className,
367                            lookForPtDoc, lookForJavadoc, lookForSource,
368                            lookForActorIndex);
369                } catch (Throwable throwable) {
370                    throw new Exception(
371                            "Failed to call doc application initializer "
372                                    + "class \""
373                                    + docApplicationSpecializerClassName
374                                    + "\" on class \"" + className + "\".");
375                }
376            }
377
378            String applicationName = "";
379
380            // We handle the applicationName specially so that we open
381            // only the docs for the app we are running.
382            try {
383                StringAttribute applicationNameAttribute = (StringAttribute) configuration
384                        .getAttribute("_applicationName",
385                                StringAttribute.class);
386
387                if (applicationNameAttribute != null) {
388                    applicationName = applicationNameAttribute.getExpression();
389                }
390            } catch (Throwable throwable) {
391                // Ignore and use the default applicationName: "",
392                // which means we look in doc.codeDoc.
393            }
394
395            // We search first on the local machine and then ask
396            // the user and then possibly look on the remote machine.
397            // So, we define the strings in an array for ease of reuse.
398
399            String docNames[] = {
400                    "doc/codeDoc"
401                            + (applicationName.equals("") ? "/"
402                                    : applicationName + "/doc/codeDoc/")
403                            + className.replace('.', '/') + ".xml",
404
405                    "doc/codeDoc/" + className.replace('.', '/') + ".xml",
406
407                    "doc/codeDoc"
408                            + (applicationName.equals("") ? "/"
409                                    : applicationName + "/doc/codeDoc/")
410                            + className.replace('.', '/') + ".html",
411
412                    "doc/codeDoc/" + className.replace('.', '/') + ".html",
413
414                    className.replace('.', '/') + ".java",
415
416                    "doc/codeDoc"
417                            + (applicationName.equals("") ? "/"
418                                    : applicationName + "/doc/codeDoc/")
419
420                            + className.replace('.', '/') + "Idx.htm" };
421
422            // List of docNames we use if we don't find anything locally.
423            List docNameList = new LinkedList();
424
425            // We look for the documentation relative to this classLoader.n
426            ClassLoader referenceClassLoader = Class
427                    .forName("ptolemy.vergil.actor.DocManager")
428                    .getClassLoader();
429
430            // Rather than using a deeply nested set of if/else's, we
431            // just keep checking toRead == null.p
432
433            // If applicationName is not "", then look in
434            // doc/codeDoc_applicationName/doc/codeDoc.
435            if (toRead == null && lookForPtDoc) {
436                docNameList.add(docNames[0]);
437                toRead = referenceClassLoader.getResource(docNames[0]);
438            }
439
440            if (toRead == null && lookForPtDoc && !applicationName.equals("")) {
441                // applicationName was set, try looking in the
442                // documentation for the default application (vergil).
443                docNameList.add(docNames[1]);
444                toRead = referenceClassLoader.getResource(docNames[1]);
445            }
446
447            if (toRead == null && lookForJavadoc) {
448                // If the class does not extend NamedObj, try to open
449                // the javadoc .html
450
451                //If we are searching for documentation and the ptdoc
452                //.xml file is not present, but the javadoc .html file
453                //is present, then display the .html file.  This is
454                //necessary to support links to classes that are not
455                //present in the models because we only generate ptdoc
456                //.xml files for classes that are in the models.
457
458                try {
459
460                    Class.forName(className);
461
462                    //if (!_namedObjClass.isAssignableFrom(targetClass)
463                    //|| !lookForPtDoc) {
464
465                    // Look in the Application specific codeDoc directory.
466                    docNameList.add(docNames[2]);
467                    toRead = referenceClassLoader.getResource(docNames[2]);
468                    if (toRead == null) {
469                        // Try looking in the documentation for vergil.
470                        docNameList.add(docNames[3]);
471                        toRead = referenceClassLoader.getResource(docNames[3]);
472                    }
473                    //}
474                } catch (ClassNotFoundException ex) {
475                    // Ignore, we could have the Sinewave Actor oriented class.
476                }
477            }
478
479            if (toRead == null && lookForSource && !lookForPtDoc
480                    && !lookForJavadoc) {
481                // Look for the source _ONLY_ if we are not looking for
482                // ptdoc or javadoc.
483                docNameList.add(docNames[4]);
484                toRead = referenceClassLoader.getResource(docNames[4]);
485            }
486
487            if (toRead == null && lookForActorIndex) {
488                // Look for the list of demos
489                if (!className.equals("org.terraswarm.accessor.JSAccessor")) {
490                    docNameList.add(docNames[5]);
491                    // } else {
492                    //     // Get the script parameter and look for an adjacent *Idx.htm file.
493                    //     try {
494                    //         Parameter accessorSourceParameter = (Parameter) _target.getProperty("accessorSource");
495                    //         if (accessorSourceParameter != null) {
496                    //             String acccessorSource = accessorSourceParameter.getExpression();
497                    //             docNameList.add(accessorSource.substring(0, accessorSource.length() - 3 + "Idx.html"));
498                    //         }
499                    //     } catch (Throwable throwable) {
500                    //         docNameList.add(docNames[5]);
501                    //     }
502                }
503
504                toRead = referenceClassLoader.getResource(docNames[5]);
505            }
506
507            if (toRead == null && _remoteDocumentationURLBase != null) {
508                // Try searching on a remote host.
509                // Loop through each docNamesIterator and try to open
510                // a stream.  Stop if once we open a stream.
511                Iterator docNameIterator = docNameList.iterator();
512                while (docNameIterator.hasNext()) {
513                    String docName = (String) docNameIterator.next();
514                    // Handle redirects for http -> https
515                    toRead = FileUtilities.followRedirects(
516                            new URL(_remoteDocumentationURLBase + docName));
517
518                    if (toRead != null) {
519                        InputStream toReadStream = null;
520                        try {
521                            // In an Exception, this may throw a SecurityException.
522                            toReadStream = toRead.openStream();
523                        } catch (Exception ex) {
524                            toRead = null;
525                        } finally {
526                            if (toReadStream != null) {
527                                try {
528                                    toReadStream.close();
529                                } catch (IOException ex2) {
530                                    // Ignore.
531                                }
532                            }
533                        }
534                        if (toRead != null) {
535                            break;
536                        }
537                    }
538                }
539            }
540        } catch (Exception ex) {
541            // Ignore, we did not find the class.
542            ex.printStackTrace();
543            return null;
544        }
545        return toRead;
546    }
547
548    /** Handle character data.  In this implementation, the
549     *  character data is accumulated in a buffer until the
550     *  end element.
551     *  &AElig;lfred will call this method once for each chunk of
552     *  character data found in the contents of elements.  Note that
553     *  the parser may break up a long sequence of characters into
554     *  smaller chunks and call this method once for each chunk.
555     *  @param chars The character data.
556     *  @param offset The starting position in the array.
557     *  @param length The number of characters available.
558     */
559    @Override
560    public void charData(char[] chars, int offset, int length) {
561        _currentCharData.append(chars, offset, length);
562    }
563
564    /** End the document.  In this implementation, do nothing.
565     *  &AElig;lfred will call this method once, when it has
566     *  finished parsing the XML document.
567     *  It is guaranteed that this will be the last method called.
568     */
569    @Override
570    public void endDocument() throws Exception {
571    }
572
573    /** End an element.
574     *  &AElig;lfred will call this method at the end of each element
575     *  (including EMPTY elements).
576     *  @param elementName The element type name.
577     *  @exception Exception Not thrown in this base class.
578     */
579    @Override
580    public void endElement(String elementName) throws Exception {
581        if (elementName.equals("author")) {
582            _author = _currentCharData.toString();
583        } else if (elementName.equals("description")) {
584            _description = _currentCharData.toString();
585        } else if (elementName.equals("port")) {
586            _ports.put(_name, _currentCharData.toString());
587        } else if (elementName.equals("property")) {
588            _properties.put(_name, _currentCharData.toString());
589        } else if (elementName.equals("Pt.AcceptedRating")) {
590            _ptAcceptedRating = _currentCharData.toString();
591        } else if (elementName.equals("Pt.ProposedRating")) {
592            _ptProposedRating = _currentCharData.toString();
593        } else if (elementName.equals("since")) {
594            _since = _currentCharData.toString();
595        } else if (elementName.equals("version")) {
596            _version = _currentCharData.toString();
597        }
598    }
599
600    /** Indicate a fatal XML parsing error.
601     *  &AElig;lfred will call this method whenever it encounters
602     *  a serious error.  This method simply throws an XmlException.
603     *  @param message The error message.
604     *  @param systemID The URI of the entity that caused the error.
605     *  @param line The approximate line number of the error.
606     *  @param column The approximate column number of the error.
607     *  @exception XmlException If called.
608     */
609    @Override
610    public void error(String message, String systemID, int line, int column)
611            throws XmlException {
612        throw new XmlException(message, _currentExternalEntity(), line, column);
613    }
614
615    /** Return the Pt.AcceptedRating field, or null
616     *  if none has been given. Note that unlike some of the other
617     *  fields, this does not delegate to the next tier if no
618     *  since field has been given.
619     *  @return The Pt.AcceptedRating field.
620     */
621    public String getAcceptedRating() {
622        if (_ptAcceptedRating == null) {
623            _readDocFile();
624        }
625        return _ptAcceptedRating;
626    }
627
628    /** Return the author field, or the string "No author given"
629     *  if none has been given. Note that unlike some of the other
630     *  fields, this does not delegate to the next tier if no
631     *  author has been given.
632     *  @return The author field.
633     */
634    public String getAuthor() {
635        if (_author == null) {
636            _readDocFile();
637            if (_author == null) {
638                return "No author given";
639            }
640        }
641        return _author;
642    }
643
644    /** Return the class name, or null if none has been given.
645     *  @return The class name.
646     */
647    public String getClassName() {
648        if (_className == null && _exception != null) {
649            return _exception;
650        }
651        return _className;
652    }
653
654    /** Return the description, or null if none has been given.
655     *  @return The description.
656     */
657    public String getDescription() {
658        if (_exception != null) {
659            return _exception;
660        } else if (_description == null) {
661            _readDocFile();
662            if (_description == null) {
663                _createNextTier();
664                if (_nextTier != null) {
665                    return _nextTier.getDescription();
666                } else {
667                    return "No description";
668                }
669            }
670        }
671        return _description;
672    }
673
674    /** Return next tier, if there is one.
675     *  If this is an instance, then the next tier
676     *  is the documentation for the class. If it is a
677     *  class, then the next tier is the documentation for
678     *  the superclass.
679     *  @return The next tier, or null if there isn't one.
680     */
681    public DocManager getNextTier() {
682        _createNextTier();
683        return _nextTier;
684    }
685
686    /** Return the documentation for the specified port, or null
687     *  if there is none.
688     *  @param name The name of the port.
689     *  @return The documentation for the specified port, or null
690     *   if there is none.
691     */
692    public String getPortDoc(String name) {
693        _readDocFile();
694        String result = (String) _ports.get(name);
695        if (result == null) {
696            result = (String) _ports.get(name + " (port)");
697            if (result == null) {
698                _createNextTier();
699                if (_nextTier != null) {
700                    return _nextTier.getPortDoc(name);
701                }
702            }
703        }
704        return result;
705    }
706
707    /** Return the documentation for the specified property
708     *  (parameter or attribute), or null if there is none.
709     *  @param name The name of the property.
710     *  @return The documentation for the specified property, or null
711     *   if there is none.
712     */
713    public String getPropertyDoc(String name) {
714        _readDocFile();
715        String result = (String) _properties.get(name);
716        if (result == null) {
717            result = (String) _properties.get(name + " (parameter)");
718            if (result == null) {
719                _createNextTier();
720                if (_nextTier != null) {
721                    return _nextTier.getPropertyDoc(name);
722                }
723            }
724        }
725        return result;
726    }
727
728    /** Return the Pt.ProposedRating field, or null
729     *  if none has been given. Note that unlike some of the other
730     *  fields, this does not delegate to the next tier if no
731     *  since field has been given.
732     *  @return The Pt.ProposedRating field.
733     */
734    public String getProposedRating() {
735        if (_ptProposedRating == null) {
736            _readDocFile();
737        }
738        return _ptProposedRating;
739    }
740
741    /** Return the since field, or null
742     *  if none has been given. Note that unlike some of the other
743     *  fields, this does not delegate to the next tier if no
744     *  since field has been given.
745     *  @return The since field.
746     */
747    public String getSince() {
748        if (_since == null) {
749            _readDocFile();
750        }
751        return _since;
752    }
753
754    /** Get the location of the website documentation.
755     *  The website documentation is set by the
756     *  _remoteDocumentationURLBase attribute in the configuration.
757     *  That attribute, if present, should be a parameter that whose
758     *  value is a string that represents the URL where the
759     *  documentation may be found.  If the
760     *  _remoteDocumentationURLBase attribute is not set, then the
761     *  location of the website documentation defaults to
762     *  <code>http://ptolemy.eecs.berkeley.edu/ptolemyII/ptII/<i>Major.Version</i></code>,
763     *  where <code><i>Major.Version</i></code> is the value returned by
764     *  {@link
765     *  ptolemy.kernel.attributes.VersionAttribute#majorCurrentVersion()}.
766     *  @return The URL location of the website documentation.
767     *  @see #setRemoteDocumentationURLBase(String)
768     */
769    public static String getRemoteDocumentationURLBase() {
770        return _remoteDocumentationURLBase;
771    }
772
773    /** Return the class of the target.
774     *  @return The class of the target.
775     */
776    public Class getTargetClass() {
777        return _targetClass;
778    }
779
780    /** Return the version field, or null
781     *  if none has been given. Note that unlike some of the other
782     *  fields, this does not delegate to the next tier if no
783     *  version has been given. If the version field is the standard
784     *  CVS version, then return only the version number and date.
785     *  @return The version field.
786     */
787    public String getVersion() {
788        if (_version == null) {
789            _readDocFile();
790        }
791        if (_version != null) {
792            if (_version.startsWith("$Id:")) {
793                // Standard CVS version. Extract the version number and date.
794                int index = _version.indexOf(",v ");
795                if (index > 4) {
796                    String tail = _version.substring(index + 3);
797                    // Find the first space after the start.
798                    index = tail.indexOf(" ");
799                    if (index > 0) {
800                        // Find the second space.
801                        index = tail.indexOf(" ", index + 1);
802                        if (index > 0) {
803                            _version = tail.substring(0, index);
804                        }
805                    }
806                }
807            }
808        }
809        return _version;
810    }
811
812    /** Return "see also" information. This includes a link
813     *  to the javadoc documentation, the source code, and the
814     *  superclass information.
815     *  @return The "see also" information.
816     */
817    public String getSeeAlso() {
818        StringBuffer result = new StringBuffer();
819
820        // See whether there is Javadoc, and link to it if there is.
821        result.append("<b>See Also:</b><ul>\n");
822
823        // The class name is either the name of the target class
824        // or the class name provided by the target.
825        String className;
826        if (_target == null) {
827            if (_targetClass == null) {
828                throw new NullPointerException(
829                        "Both _target and _targetClass are null?");
830            }
831            className = _targetClass.getName();
832        } else {
833            className = _target.getClassName();
834        }
835        if (_isInstanceDoc) {
836            // Create a link to the class documentation,
837            // if there is some. First not that the superclass
838            // may itself be an instance with instance documentation.
839            // In that case, the hyperlink is special and must be
840            // intercepted by the DocViewer class.
841            if (_target instanceof Instantiable
842                    && ((Instantiable) _target).getParent() != null
843                    && ((NamedObj) ((Instantiable) _target).getParent())
844                            .attributeList(DocAttribute.class).size() > 0) {
845                result.append(
846                        "<li><a href=\"#parentClass\">Class documentation</a></li>");
847            }
848            // Get either the PtDoc, javadoc, or source.
849            URL toRead = docClassNameToURL(_configuration, className, true,
850                    true, true, false);
851            if (toRead != null) {
852                result.append("<li><a href=\"" + toRead.toExternalForm());
853                if (toRead.toExternalForm().endsWith(".html")) {
854                    // Sadly, Javadoc from Java 1.7 cannot be
855                    // displayed using a JEditorPane, so we open
856                    // javadoc in an external browser.  To test this
857                    // out, see
858                    // http://docs.oracle.com/javase/tutorial/uiswing/components/editorpane.html#editorpane
859                    // and modify the example so that it tries to view
860                    // the Javadoc for Object.
861                    result.append("#in_browser\">Javadoc Documentation");
862                } else if (toRead.toExternalForm().endsWith(".java")) {
863                    result.append("\">Java Source");
864                } else if (toRead.toExternalForm().endsWith(".xml")) {
865                    result.append("\">Class Documentation");
866                }
867                result.append("</a></li>");
868            }
869        } else {
870            URL docURL = null;
871            try {
872                // Get the javadoc
873                URL toRead = docClassNameToURL(_configuration, className, false,
874                        true, false, false);
875
876                if (toRead != null) {
877                    docURL = toRead;
878                    result.append("<li><a href=\"" + toRead.toExternalForm()
879                    // Sadly, Javadoc from Java 1.7 cannot be
880                    // displayed using a JEditorPane, so we open
881                    // javadoc in an external browser.  To test this
882                    // out, see
883                    // http://docs.oracle.com/javase/tutorial/uiswing/components/editorpane.html#editorpane
884                    // and modify the example so that it tries to view
885                    // the Javadoc for Object.
886                            + "#in_browser\">Javadoc Documentation</a></li>");
887                } else {
888                    // FIXME: Make this a hyperlink to a doc on how
889                    // to create the javadocs.
890                    result.append("<li>No javadocs found</li>");
891                }
892            } catch (Exception ex) {
893                result.append("<li>Error opening javadoc file:\n<pre>" + ex
894                        + "/n</pre></li>\n");
895            }
896
897            // See whether the base class has a doc file, and if so,
898            // link to it.  If not, try to link to the Javadoc for the
899            // base class.
900            try {
901                String baseClassName = _targetClass.getSuperclass().getName();
902                URL toRead = docClassNameToURL(_configuration, baseClassName,
903                        true, true, true, false);
904
905                // Display only the unqualified class name for compactness.
906                int lastDot = baseClassName.lastIndexOf(".");
907                if (lastDot >= 0) {
908                    baseClassName = baseClassName.substring(lastDot + 1);
909                }
910                if (toRead != null
911                        && toRead.toExternalForm().endsWith(".xml")) {
912                    result.append("<li><a href=\"" + toRead.toExternalForm()
913                            + "\">Base class (" + baseClassName + ")</a></li>");
914                } else if (toRead != null
915                        && toRead.toExternalForm().endsWith(".html")) {
916                    result.append("<li><a href=\"" + toRead.toExternalForm()
917                            + "#in_browser\">Base class Javadoc ("
918                            + baseClassName + ")</a></li>");
919                } else if (toRead != null
920                        && toRead.toExternalForm().endsWith(".java")) {
921                    result.append("<li><a href=\"" + toRead.toExternalForm()
922                            + "\">Base class Java (" + baseClassName
923                            + ")</a></li>");
924                }
925            } catch (Exception ex) {
926                result.append("<li>Error opening javadoc file:\n<pre>" + ex
927                        + "/n</pre></li>\n");
928            }
929
930            // Link to the source code, if present.
931            try {
932                URL toRead = docClassNameToURL(_configuration, className, false,
933                        false, true, false);
934                if (toRead != null) {
935                    String modificationMessage = "";
936                    try {
937                        if (toRead.toExternalForm().startsWith("file:/")
938                                && docURL.toExternalForm()
939                                        .startsWith("file:/")) {
940                            // Check the mod times and print a message if the doc file
941                            // it out of date.
942                            File sourceFile = new File(toRead.getFile());
943                            File docFile = new File(docURL.getFile());
944                            if (sourceFile.lastModified() > docFile
945                                    .lastModified()) {
946                                modificationMessage = "<font color=\"red\">Documentation "
947                                        + "may be out of date when compared to source.</font> "
948                                        + "<br/>The source was last modified on <br/>"
949                                        + new Date(sourceFile.lastModified())
950                                        + ",<br/> documentation was last modified on <br/>"
951                                        + new Date(docFile.lastModified())
952                                        + ".<br/> To rebuild the documentation use the "
953                                        + "Build menu choice.";
954                            }
955                        }
956                    } catch (Exception ex) {
957                        // Ignore
958                    }
959                    result.append("<li><a href=\"" + toRead.toExternalForm()
960                            + "\">Source code</a>" + modificationMessage
961                            + "</li>");
962                }
963            } catch (Exception ex) {
964                // Do not report anything.
965            }
966        }
967
968        // FIXME: Need see also fields from the doclet analysis.
969        // FIXME: Include demos? How?
970
971        try {
972            URL toRead = docClassNameToURL(_configuration, className, false,
973                    false, false, true);
974            System.out.println("DocManager: Trying to find demos for "
975                    + className + ", _target: " + _target + " _targetClass: "
976                    + _targetClass + ": toRead: " + toRead);
977            if (toRead != null) {
978                result.append("<li><a href=\"" + toRead.toExternalForm()
979                        + "\">Demo Usage</a></li>");
980            } else {
981                result.append("<li>Not found in any demos</li>");
982            }
983        } catch (Exception ex) {
984            // Do not report anything.
985
986        }
987        result.append("</ul>");
988        return result.toString();
989    }
990
991    /** Return true if an exception was encountered parsing
992     *  the DocML data.
993     *  @return True if an exception was encountered.
994     */
995    public boolean hadException() {
996        return _exception != null;
997    }
998
999    /** Return true if the primary source of documentation is
1000     *  the instance. That is, return true if the target has
1001     *  an instance of DocAttribute in it, and at least one
1002     *  of the fields of the DocAttribute is not empty.
1003     *  @return True if this documents an instance (vs. a class).
1004     */
1005    public boolean isInstanceDoc() {
1006        return _isInstanceDoc;
1007    }
1008
1009    /** Return true if the target class is a subclass of Attribute
1010     *  that has a two-argument constructor compatible where the
1011     *  first argument is a CompositeEntity and the second is a
1012     *  String. This will return true if the target is itself
1013     *  an instance of Attribute or a subclass.
1014     *  @return True if the target is an instantiable attribute.
1015     */
1016    public boolean isTargetInstantiableAttribute() {
1017        if (_target != null) {
1018            return _target instanceof Attribute;
1019        } else {
1020            Class targetClass = _targetClass;
1021            while (targetClass != null) {
1022                if (targetClass.equals(Attribute.class)) {
1023                    return _hasMoMLConstructor();
1024                }
1025                targetClass = targetClass.getSuperclass();
1026            }
1027            return false;
1028        }
1029    }
1030
1031    /** Return true if the target class is a subclass of Entity
1032     *  that has a two-argument constructor compatible where the
1033     *  first argument is a CompositeEntity and the second is a
1034     *  String. This will return true if the target is itself
1035     *  an instance of Entity or a subclass.
1036     *  @return True if the target is an instantiable entity.
1037     */
1038    public boolean isTargetInstantiableEntity() {
1039        if (_target != null) {
1040            return _target instanceof Entity;
1041        } else {
1042            Class targetClass = _targetClass;
1043            while (targetClass != null) {
1044                if (targetClass.equals(Entity.class)) {
1045                    return _hasMoMLConstructor();
1046                }
1047                targetClass = targetClass.getSuperclass();
1048            }
1049            return false;
1050        }
1051    }
1052
1053    /** Return true if the target class is a subclass of Port
1054     *  that has a two-argument constructor compatible where the
1055     *  first argument is a CompositeEntity and the second is a
1056     *  String. This will return true if the target is itself
1057     *  an instance of Port or a subclass.
1058     *  @return True if the target is an instantiable port.
1059     */
1060    public boolean isTargetInstantiablePort() {
1061        if (_target != null) {
1062            return _target instanceof Port;
1063        } else {
1064            Class targetClass = _targetClass;
1065            while (targetClass != null) {
1066                if (targetClass.equals(Port.class)) {
1067                    return _hasMoMLConstructor();
1068                }
1069                targetClass = targetClass.getSuperclass();
1070            }
1071            return false;
1072        }
1073    }
1074
1075    /** Parse the given stream as a DocML file.
1076     *  For example, a user might use this method as follows:
1077     *  <pre>
1078     *     DocManager parser = new DocManager();
1079     *     URL xmlFile = new URL(null, docURL);
1080     *     parser.parse(xmlFile.openStream());
1081     *  </pre>
1082     *  A variety of exceptions might be thrown if the parsed
1083     *  data does not represent a valid DocML file.
1084     *  @param base The base URL from which the XML is read.
1085     *  @param input The stream from which to read XML.
1086     *  @exception Exception If the parser fails.
1087     */
1088    public void parse(URL base, InputStream input) throws Exception {
1089        parse(base, new InputStreamReader(input,
1090                java.nio.charset.Charset.defaultCharset()));
1091    }
1092
1093    /** Parse the given stream as a DocML file.
1094     *  A variety of exceptions might be thrown if the parsed
1095     *  data does not represent a valid DocML file.
1096     *  @param base The base URL from which the XML is read.
1097     *  @param reader The stream from which to read XML.
1098     *  @exception Exception If the parser fails.
1099     */
1100    public void parse(URL base, Reader reader) throws Exception {
1101        _parser.setHandler(this);
1102
1103        Reader buffered = new BufferedReader(reader);
1104
1105        if (base == null) {
1106            _parser.parse(null, null, buffered);
1107        } else {
1108            _parser.parse(base.toExternalForm(), null, buffered);
1109        }
1110    }
1111
1112    /** Parse the given text as DocML.
1113     *  A variety of exceptions might be thrown if the parsed
1114     *  data does not represent valid DocML data.
1115     *  @param text The DocML data.
1116     *  @exception Exception If the parser fails.
1117     */
1118    public void parse(String text) throws Exception {
1119        parse(null, new StringReader(text));
1120    }
1121
1122    /** Resolve an external entity. If the first argument is the
1123     *  name of the DocML PUBLIC DTD ("-//UC Berkeley//DTD DocML 1//EN"),
1124     *  then return a StringReader
1125     *  that will read the locally cached version of this DTD
1126     *  (the public variable DocML_DTD_1). Otherwise, return null,
1127     *  which has the effect of deferring to &AElig;lfred for
1128     *  resolution of the URI.  Derived classes may return a
1129     *  a modified URI (a string), an InputStream, or a Reader.
1130     *  In the latter two cases, the input character stream is
1131     *  provided.
1132     *  @param publicID The public identifier, or null if none was supplied.
1133     *  @param systemID The system identifier.
1134     *  @return Null, indicating to use the default system identifier.
1135     */
1136    @Override
1137    public Object resolveEntity(String publicID, String systemID) {
1138        if (publicID != null
1139                && publicID.equals("-//UC Berkeley//DTD DocML 1//EN")) {
1140            // This is the generic DocML DTD.
1141            return new StringReader(DocML_DTD_1);
1142        } else {
1143            return null;
1144        }
1145    }
1146
1147    /** Set the location of the remote documentation.
1148     *  @param remoteDocumentationURLBase The remote location of the class
1149     *  documentation.
1150     *  @see #getRemoteDocumentationURLBase()
1151     */
1152    public static void setRemoteDocumentationURLBase(
1153            String remoteDocumentationURLBase) {
1154        System.out.println("DocManager.setRemoteDocumentationURLBase: "
1155                + remoteDocumentationURLBase);
1156        _remoteDocumentationURLBase = remoteDocumentationURLBase;
1157    }
1158
1159    /** Start a document.  This method is called just before the parser
1160     *  attempts to read the first entity (the root of the document).
1161     *  It is guaranteed that this will be the first method called.
1162     */
1163    @Override
1164    public void startDocument() {
1165        _attributes = new HashMap();
1166    }
1167
1168    /** Start an element.
1169     *  This is called at the beginning of each XML
1170     *  element.  By the time it is called, all of the attributes
1171     *  for the element will already have been reported using the
1172     *  attribute() method.  Unrecognized elements are ignored.
1173     *  @param elementName The element type name.
1174     *  @exception XmlException If the element produces an error
1175     *   in constructing the model.
1176     */
1177    @Override
1178    public void startElement(String elementName) throws XmlException {
1179        try {
1180            // NOTE: The elements are alphabetical below...
1181            if (elementName.equals("author")
1182                    || elementName.equals("description")
1183                    || elementName.equals("Pt.AcceptedRating")
1184                    || elementName.equals("Pt.ProposedRating")
1185                    || elementName.equals("since")
1186                    || elementName.equals("version")) {
1187                _currentCharData = new StringBuffer();
1188            } else if (elementName.equals("doc")) {
1189                _className = (String) _attributes.get("class");
1190                _checkForNull(_className,
1191                        "No class argument for element \"doc\"");
1192                Class specifiedClass = Class.forName(_className);
1193                if (_targetClass != null && _targetClass != specifiedClass) {
1194                    throw new Exception("Classes don't match: " + _targetClass
1195                            + "\n and \n" + specifiedClass);
1196                }
1197                _targetClass = specifiedClass;
1198            } else if (elementName.equals("port")) {
1199                _currentCharData = new StringBuffer();
1200                _name = (String) _attributes.get("name");
1201                _checkForNull(_name, "No name argument for element \"port\"");
1202            } else if (elementName.equals("property")) {
1203                _currentCharData = new StringBuffer();
1204                _name = (String) _attributes.get("name");
1205                _checkForNull(_name,
1206                        "No name argument for element \"property\"");
1207            }
1208        } catch (Exception ex) {
1209            if (ex instanceof XmlException) {
1210                throw (XmlException) ex;
1211            } else {
1212                String msg = "XML element \"" + elementName
1213                        + "\" triggers exception:\n  " + ex.toString();
1214                throw new XmlException(msg, _currentExternalEntity(),
1215                        _parser.getLineNumber(), _parser.getColumnNumber());
1216            }
1217        }
1218        _attributes.clear();
1219    }
1220
1221    /** Handle the start of an external entity.  This pushes the stack so
1222     *  that error reporting correctly reports the external entity that
1223     *  causes the error.
1224     *  @param systemID The URI for the external entity.
1225     */
1226    @Override
1227    public void startExternalEntity(String systemID) {
1228        _externalEntities.push(systemID);
1229    }
1230
1231    ///////////////////////////////////////////////////////////////////
1232    ////                         public members                    ////
1233
1234    /** The standard DocML DTD, represented as a string.  This is used
1235     *  to parse DocML data when a compatible PUBLIC DTD is specified.
1236     */
1237    public static final String DocML_DTD_1 = "<!ELEMENT doc (author | description | port | property | Pt.AcceptedRating | Pt.ProposedRating | since | version)*><!ATTLIST doc name CDATA #REQUIRED class CDATA #REQUIRED><!ELEMENT author (#PCDATA)><!ELEMENT description (#PCDATA)><!ELEMENT port (#PCDATA)><!ATTLIST port name CDATA #REQUIRED><!ELEMENT property (#PCDATA)><!ATTLIST property name CDATA #REQUIRED><!ELEMENT Pt.acceptedRating (#PCDATA)><!ELEMENT Pt.proposedRating (#PCDATA)><!ELEMENT since (#PCDATA)><!ELEMENT version (#PCDATA)>";
1238
1239    // NOTE: The master file for the above DTD is at
1240    // $PTII/ptolemy/vergil/basic/DocML_1.dtd.  If modified, it needs to be also
1241    // updated at http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd
1242
1243    ///////////////////////////////////////////////////////////////////
1244    ////                         protected methods                 ////
1245
1246    /** If the argument is null, throw an exception with the given message.
1247     *  @param object The reference to check for null.
1248     *  @param message The message to issue if the reference is null.
1249     *  @exception XmlException If the object parameter is null.
1250     */
1251    protected void _checkForNull(Object object, String message)
1252            throws XmlException {
1253        if (object == null) {
1254            throw new XmlException(message, _currentExternalEntity(),
1255                    _parser.getLineNumber(), _parser.getColumnNumber());
1256        }
1257    }
1258
1259    /** Get the the URI for the current external entity.
1260     *  @return A string giving the URI of the external entity being read,
1261     *   or null if none.
1262     */
1263    protected String _currentExternalEntity() {
1264        return (String) _externalEntities.peek();
1265    }
1266
1267    ///////////////////////////////////////////////////////////////////
1268    ////                         private methods                   ////
1269
1270    /** Create next tier, if possible. */
1271    private void _createNextTier() {
1272        if (_nextTier != null) {
1273            return;
1274        }
1275        if (_isInstanceDoc) {
1276            _nextTier = new DocManager(_configuration, _targetClass);
1277        } else {
1278            Class superClass = _targetClass.getSuperclass();
1279            if (_isNamedObj(superClass)) {
1280                _nextTier = new DocManager(_configuration, superClass);
1281            }
1282        }
1283    }
1284
1285    /** Return true if the target class has a two argument
1286     *  constructor compatible with MoML instantiation.
1287     *  @return True if the target class can be instantiated
1288     *   by MoML in a CompositeEntity.
1289     */
1290    private boolean _hasMoMLConstructor() {
1291        // Check for a suitable constructor.
1292        Class[] parameters = { TypedCompositeActor.class, String.class };
1293        while (parameters[0] != null) {
1294            try {
1295                _targetClass.getConstructor(parameters);
1296                // If we get here, then there is such a constructor.
1297                return true;
1298            } catch (Exception e) {
1299                // Ignore and try the superclass.
1300            }
1301            if (parameters[0].equals(NamedObj.class)) {
1302                break;
1303            }
1304            parameters[0] = parameters[0].getSuperclass();
1305        }
1306        return false;
1307    }
1308
1309    /** Initialize fields.  We assume _target was set by the caller
1310
1311     private void _init() {
1312
1313     _isInstanceDoc = false;
1314     }
1315    */
1316
1317    /** Return true if the specified class is either equal to
1318     *  NamedObj or is a subclass of NamedObj.
1319     */
1320    private boolean _isNamedObj(Class candidate) {
1321        if (candidate == NamedObj.class) {
1322            return true;
1323        } else {
1324            candidate = candidate.getSuperclass();
1325            if (candidate == null) {
1326                return false;
1327            }
1328            return _isNamedObj(candidate);
1329        }
1330    }
1331
1332    /** Read the doc file, if one is found, for the target. */
1333    private void _readDocFile() {
1334        if (_docFileHasBeenRead || _isInstanceDoc) {
1335            return;
1336        }
1337        // FIXME: If file is not found, then instead of an
1338        // exception, probably want to delegate to the base class.
1339        // Read the .xml file, but not the java or source
1340        URL toRead = docClassNameToURL(_configuration, _className, true, false,
1341                false, false);
1342
1343        try {
1344            if (toRead != null) {
1345                parse(null, toRead.openStream());
1346            }
1347        } catch (Exception ex) {
1348            ex.printStackTrace();
1349            _exception = "Error reading URL: " + toRead.toExternalForm()
1350                    + "\n<pre>\n" + ex + "\n</pre>\n";
1351        }
1352        _docFileHasBeenRead = true;
1353    }
1354
1355    ///////////////////////////////////////////////////////////////////
1356    ////                         private members                   ////
1357
1358    /** Attributes associated with an entity. */
1359    private HashMap _attributes;
1360
1361    /** The author field. */
1362    private String _author;
1363
1364    /** The class name. */
1365    private String _className;
1366
1367    /**  The configuration in which to look up the
1368     *  _docApplicationSpecializer and _applicationName parameters.
1369     */
1370    private Configuration _configuration;
1371
1372    /** The current character data for the current element. */
1373    private StringBuffer _currentCharData = new StringBuffer();
1374
1375    /** The description field. */
1376    private String _description;
1377
1378    /** Indicator that the doc file has been read. */
1379    private boolean _docFileHasBeenRead = false;
1380
1381    /** If an exception is encountered parsing, it is described here. */
1382    private String _exception;
1383
1384    /** The external entities being parsed. */
1385    private Stack _externalEntities = new Stack();
1386
1387    /** Indicator that the primary source of documentation is the instance. */
1388    private boolean _isInstanceDoc = false;
1389
1390    /** The name associated with the current port, parameter, etc. */
1391    private String _name;
1392
1393    /** The next tier in the class hierarchy. */
1394    private DocManager _nextTier;
1395
1396    /** The parser. */
1397    private XmlParser _parser = new XmlParser();
1398
1399    /** A table of property documents. */
1400    private HashMap _properties = new HashMap();
1401
1402    /** A table of port documents. */
1403    private HashMap _ports = new HashMap();
1404
1405    /** The Pt.AcceptedRating field. */
1406    private String _ptAcceptedRating;
1407
1408    /** The Pt.ProposedRating field. */
1409    private String _ptProposedRating;
1410
1411    /** The location of the website documentation.
1412     */
1413    private static String _remoteDocumentationURLBase;
1414
1415    /** The since field. */
1416    private String _since;
1417
1418    /** The object to be documented. */
1419    private NamedObj _target;
1420
1421    /** The class of object to be documented. */
1422    private Class _targetClass;
1423
1424    /** The version field. */
1425    private String _version;
1426}