001/* A documentation attribute for Kepler.
002
003 Copyright (c) 2007-2014 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 */
028
029package ptolemy.vergil.basic;
030
031import java.io.IOException;
032import java.io.Writer;
033import java.util.Enumeration;
034import java.util.Hashtable;
035import java.util.Iterator;
036
037import ptolemy.data.expr.StringParameter;
038import ptolemy.kernel.Entity;
039import ptolemy.kernel.Port;
040import ptolemy.kernel.util.Attribute;
041import ptolemy.kernel.util.Configurable;
042import ptolemy.kernel.util.ConfigurableAttribute;
043import ptolemy.kernel.util.IllegalActionException;
044import ptolemy.kernel.util.InternalErrorException;
045import ptolemy.kernel.util.NameDuplicationException;
046import ptolemy.kernel.util.NamedObj;
047import ptolemy.kernel.util.StringAttribute;
048import ptolemy.kernel.util.Workspace;
049import ptolemy.moml.MoMLChangeRequest;
050import ptolemy.util.StringUtilities;
051
052/**
053 A Documentation attribute for actors.
054 This class is used by Kepler so that the DocViewer can access kepler
055 specific actor metadata based documentation.
056 @author Chad Berkley
057 @version $Id$
058 @since Ptolemy II 6.1
059 @Pt.ProposedRating Red (cxh)
060 @Pt.AcceptedRating Red (cxh)
061 */
062public class KeplerDocumentationAttribute extends Attribute
063        implements Configurable {
064
065    /** Construct a Kepler documentation attribute.  */
066    public KeplerDocumentationAttribute() {
067        super();
068    }
069
070    /**
071     * Construct a Kepler documentation attribute.
072     *
073     *@param container The container.
074     *@param name The name of the Kepler documentation attribute.
075     *@exception IllegalActionException If thrown by the superclass.
076     *@exception NameDuplicationException  If thrown by the superclass.
077     */
078    public KeplerDocumentationAttribute(NamedObj container, String name)
079            throws IllegalActionException, NameDuplicationException {
080        super(container, name);
081    }
082
083    /**
084     * Construct a Kepler documentation attribute.
085     *
086     *@param workspace The workspace in which the object is created.
087     */
088    public KeplerDocumentationAttribute(Workspace workspace) {
089        super(workspace);
090    }
091
092    ///////////////////////////////////////////////////////////////////
093    ////                         public methods                    ////
094
095    /** Add port to the port hashtable.
096     *  @param name The name of the port.
097     *  @param value A String representing the port.
098     *  @exception NameDuplicationException If thrown while creating the port.
099     *  @exception Exception If thrown while configuring the port
100     */
101    public void addPort(String name, String value)
102            throws NameDuplicationException, Exception {
103
104        _portHash.put(name, value);
105        ConfigurableAttribute port = new ConfigurableAttribute(this,
106                "port:" + name);
107        port.configure(null, null, value);
108    }
109
110    /** Add a property to the property hashtable.
111     *  @param name The name of the property.
112     *  @param value A string representing the property.
113     *  @exception NameDuplicationException If thrown while creating the property
114     *  @exception Exception If thrown while configuring the attribute
115     *  @see #getProperty(String)
116     */
117    public void addProperty(String name, String value)
118            throws NameDuplicationException, Exception {
119        _propertyHash.put(name, value);
120        ConfigurableAttribute attribute = new ConfigurableAttribute(this,
121                "prop:" + name);
122        attribute.configure(null, null, value);
123    }
124
125    /** Configure this documentation attribute.
126     *  @param base Currently ignored.
127     *  @param source The source of this configuration.
128     *  @param text The configuration text.
129     */
130    @Override
131    public void configure(java.net.URL base, String source, String text) {
132        this.source = source;
133        this.text = text;
134    }
135
136    /**
137     * Create empty fields for the main attribute as well as any
138     * params or ports that exist in the target.
139     * @param target the namedobj to create the empty attributes for
140     */
141    public void createEmptyFields(NamedObj target) {
142        try {
143            /*ConfigurableAttribute authorAtt =*/new ConfigurableAttribute(this,
144                    "author");
145            /*ConfigurableAttribute versionAtt =*/new ConfigurableAttribute(
146                    this, "version");
147            /*ConfigurableAttribute descriptionAtt =*/new ConfigurableAttribute(
148                    this, "description");
149            /*ConfigurableAttribute uldAtt =*/new ConfigurableAttribute(this,
150                    "userLevelDocumentation");
151
152            _author = "";
153            _version = "";
154            _description = "";
155            _userLevelDocumentation = "";
156
157            Iterator attributes = target.attributeList().iterator();
158            while (attributes.hasNext()) {
159                Attribute attribute = (Attribute) attributes.next();
160                String attributeName = attribute.getName();
161                if (!attributeName.substring(0, 1).equals("_")
162                        && !attributeName.equals("KeplerDocumentation")) {
163                    _propertyHash.put(attribute.getName(), "");
164                }
165            }
166
167            if (target instanceof Entity) {
168                Iterator ports = ((Entity) target).portList().iterator();
169                while (ports.hasNext()) {
170                    Port p = (Port) ports.next();
171                    _portHash.put(p.getName(), "");
172                }
173            }
174
175            // Generate a generic change request so the KeplerLSID is updated
176            String caStr = "ptolemy.kernel.util.ConfigurableAttribute";
177
178            String updateMoml = "<property name=\"author\" " + "class=\""
179                    + caStr + "\" value=\"\"/>";
180            MoMLChangeRequest updateRequest = new MoMLChangeRequest(this,
181                    target, updateMoml);
182            this.requestChange(updateRequest);
183        } catch (Exception ex) {
184            throw new InternalErrorException(this, ex,
185                    "Could not add KeplerDocumentation internal attributes.");
186        }
187    }
188
189    /**
190     * Populate the members of KeplerDocumentationAttribute from
191     * another given KeplerDocumentationAttribute.
192     * @param documentationAttribute The DocumentationAttribute from which to copy attributes.
193     */
194    public void createInstanceFromExisting(
195            KeplerDocumentationAttribute documentationAttribute) {
196        if (documentationAttribute != null
197                && documentationAttribute.attributeList() != null) {
198            Iterator attributes = documentationAttribute.attributeList()
199                    .iterator();
200            while (attributes.hasNext()) {
201                ConfigurableAttribute attribute = (ConfigurableAttribute) attributes
202                        .next();
203                String attributeName = attribute.getName();
204                if (attributeName.equals("description")) {
205                    _description = attribute.getConfigureText();
206                } else if (attributeName.equals("author")) {
207                    _author = attribute.getConfigureText();
208                } else if (attributeName.equals("version")) {
209                    _version = attribute.getConfigureText();
210                } else if (attributeName.equals("userLevelDocumentation")) {
211                    _userLevelDocumentation = attribute.getConfigureText();
212                } else if (attributeName.indexOf("port:") != -1) { //add to the port hash
213                    String portName = attributeName.substring(
214                            attributeName.indexOf(":") + 1,
215                            attributeName.length());
216                    String portDescription = attribute.getConfigureText();
217                    if (portName != null) {
218                        if (portDescription == null) {
219                            portDescription = "";
220                        }
221                        _portHash.put(portName, portDescription);
222                    }
223
224                } else if (attributeName.indexOf("prop:") != -1) { //add to the prop hash
225                    String propertyName = attributeName.substring(
226                            attributeName.indexOf(":") + 1,
227                            attributeName.length());
228                    String propertyDescription = attribute.getConfigureText();
229                    if (propertyName != null) {
230                        if (propertyDescription == null) {
231                            propertyDescription = "";
232                        }
233                        _propertyHash.put(propertyName, propertyDescription);
234                    }
235                }
236            }
237        }
238    }
239
240    /** Write a MoML description of this object with the specified
241     *  indentation depth and with the specified name substituting
242     *  for the name of this object.
243     *  @param output The output stream to write to.
244     *  @param depth The depth in the hierarchy, to determine indenting.
245     *  @param name The name to use in the exported MoML.
246     *  @exception IOException If an I/O error occurs.
247     */
248    @Override
249    public void exportMoML(Writer output, int depth, String name)
250            throws IOException {
251        createInstanceFromExisting(this);
252        StringBuffer results = new StringBuffer("<property name=\"" + name
253                + "\" class=\"" + getClassName() + "\">\n"
254                + "<property name=\"description\" class=\"ptolemy.kernel.util.ConfigurableAttribute\">"
255                + "<configure>" + StringUtilities.escapeForXML(_description)
256                + "</configure>" + "</property>\n"
257                + "<property name=\"author\" class=\"ptolemy.kernel.util.ConfigurableAttribute\">"
258                + "<configure>" + StringUtilities.escapeForXML(_author)
259                + "</configure>" + "</property>\n"
260                + "<property name=\"version\" class=\"ptolemy.kernel.util.ConfigurableAttribute\">"
261                + "<configure>" + StringUtilities.escapeForXML(_version)
262                + "</configure>" + "</property>\n"
263                + "<property name=\"userLevelDocumentation\" class=\"ptolemy.kernel.util.ConfigurableAttribute\">"
264                + "<configure>"
265                + StringUtilities.escapeForXML(_userLevelDocumentation)
266                + "</configure>" + "</property>\n");
267
268        Enumeration portKeys = _portHash.keys();
269        while (portKeys.hasMoreElements()) {
270            String key = (String) portKeys.nextElement();
271            String val = (String) _portHash.get(key);
272            results.append("<property name=\"port:" + key
273                    + "\" class=\"ptolemy.kernel.util.ConfigurableAttribute\">"
274                    + "<configure>" + StringUtilities.escapeForXML(val)
275                    + "</configure>" + "</property>\n");
276        }
277
278        Enumeration propKeys = _propertyHash.keys();
279        while (propKeys.hasMoreElements()) {
280            String key = (String) propKeys.nextElement();
281            String val = (String) _propertyHash.get(key);
282            results.append("<property name=\"prop:" + key
283                    + "\" class=\"ptolemy.kernel.util.ConfigurableAttribute\">"
284                    + "<configure>" + StringUtilities.escapeForXML(val)
285                    + "</configure>" + "</property>\n");
286        }
287
288        results.append("</property>");
289        output.write(results.toString());
290    }
291
292    /** Return the author.
293     * @return the author
294     *  @see #setAuthor(String)
295     */
296    public String getAuthor() {
297        if (_author == null) {
298            return "";
299        }
300
301        if (!_author.equals("null")) {
302            return _author;
303        } else {
304            return "";
305        }
306    }
307
308    /** Get the configuration source.
309     *  @return The configuration source.
310     */
311    @Override
312    public String getConfigureSource() {
313        return source;
314    }
315
316    /** Get the configuration text.
317     *  @return The configuration text
318     */
319    @Override
320    public String getConfigureText() {
321        return text;
322    }
323
324    /** Return the description.
325     *  @return the description
326     *  @see #setDescription(String)
327     */
328    public String getDescription() {
329        if (_description == null) {
330            return "";
331        }
332
333        if (!_description.equals("null")) {
334            return _description;
335        } else {
336            return "";
337        }
338    }
339
340    /**
341     * Return a docAttribute with the available kepler documentation.
342     * Returns null if an error prevents the doc attribute from being
343     * created.
344     * @param target The container for the DocAttribute
345     * @return The DocAttribute.
346     */
347    public DocAttribute getDocAttribute(NamedObj target) {
348        createInstanceFromExisting(this);
349        try {
350            DocAttribute documentationAttribute = new DocAttribute(
351                    target.workspace());
352            documentationAttribute.setContainer(target);
353            //documentationAttribute.setName("keplerFormattedPTIIDocumentation");
354            documentationAttribute.author = new StringAttribute(
355                    documentationAttribute, "author");
356            documentationAttribute.author.setExpression(_author);
357            documentationAttribute.version = new StringAttribute(
358                    documentationAttribute, "version");
359            documentationAttribute.version.setExpression(_version);
360            documentationAttribute.since = new StringAttribute(
361                    documentationAttribute, "since");
362            documentationAttribute.since.setExpression("");
363            documentationAttribute.description = new StringAttribute(
364                    documentationAttribute, "description");
365            documentationAttribute.description
366                    .setExpression(_userLevelDocumentation);
367
368            //add ports and params
369            Enumeration ports = _portHash.keys();
370            while (ports.hasMoreElements()) {
371                String name = (String) ports.nextElement();
372                String description = (String) _portHash.get(name);
373                StringAttribute attribute = new StringAttribute(
374                        documentationAttribute, name + " (port)");
375                attribute.setExpression(description);
376            }
377
378            Enumeration propItt = _propertyHash.keys();
379            while (propItt.hasMoreElements()) {
380                String name = (String) propItt.nextElement();
381                String description = (String) _propertyHash.get(name);
382                StringParameter parameter = new StringParameter(
383                        documentationAttribute, name + " (parameter)");
384                parameter.setExpression(description);
385            }
386
387            return documentationAttribute;
388        } catch (Exception ex) {
389            throw new InternalErrorException(this, ex,
390                    "Error creating docAttribute.");
391        }
392
393    }
394
395    /** Return the document class.
396     *  @return the document class or the empty string.
397     *  @see #setDocClass(String)
398     */
399    public String getDocClass() {
400        if (!_docClass.equals("null")) {
401            return _docClass;
402        } else {
403            return "";
404        }
405    }
406
407    /** Return the document name.
408     *  @return the document name
409     *  @see #setDocName(String)
410     */
411    public String getDocName() {
412        if (!_docName.equals("null")) {
413            return _docName;
414        } else {
415            return "";
416        }
417    }
418
419    /** Return the port documentation.
420     *  @param name The name of the port.
421     * @return the port documentation.
422     */
423    public String getPort(String name) {
424        return (String) _portHash.get(name);
425    }
426
427    /** Return the port hash.
428     *  @return the port hash
429     *  @see #setPortHash(Hashtable)
430     */
431    public Hashtable getPortHash() {
432        return _portHash;
433    }
434
435    /** Return the property documentation.
436     *  @param name The name of the property.
437     *  @return the property docs
438     *  @see #addProperty(String, String)
439     */
440    public String getProperty(String name) {
441        return (String) _propertyHash.get(name);
442    }
443
444    /** Return the property hash.
445     *  @return the property hash
446     *  @see #setPropertyHash(Hashtable)
447     */
448    public Hashtable getPropertyHash() {
449        return _propertyHash;
450    }
451
452    /** Return the user level documentation.
453     *  @return the user level documentation
454     *  @see #setUserLevelDocumentation(String)
455     */
456    public String getUserLevelDocumentation() {
457        if (_userLevelDocumentation == null) {
458            return "";
459        }
460
461        if (!_userLevelDocumentation.equals("null")) {
462            return _userLevelDocumentation;
463        } else {
464            return "";
465        }
466    }
467
468    /** Return the version.
469     *  @return the version
470     *  @see #setVersion(String)
471     */
472    public String getVersion() {
473        if (_version == null) {
474            return "";
475        }
476
477        if (!_version.equals("null")) {
478            return _version;
479        } else {
480            return "";
481        }
482    }
483
484    /** Remove a port from the port hashtable.
485     *  @param name The name of the port.
486     *  @return The value of the port.
487     *  @exception IllegalActionException If an error occurs removing the
488     *  ConfigurableAttribute.
489     *  @exception NameDuplicationException If an error occurs removing the
490     *  ConfigurableAttribute.
491     *  @see #addPort(String, String)
492     */
493    public String removePort(String name)
494            throws IllegalActionException, NameDuplicationException {
495        String retval = (String) _portHash.remove(name);
496        if (retval != null) {
497            Attribute attribute = getAttribute("port:" + name);
498            attribute.setContainer(null);
499        }
500        return retval;
501    }
502
503    /** Remove a property from the property hashtable.
504     *  @param name The name of the property.
505     *  @return The value of the property.
506     *  @exception IllegalActionException If an error occurs removing the
507     *  ConfigurableAttribute.
508     *  @exception NameDuplicationException If an error occurs removing the
509     *  ConfigurableAttribute.
510     *  @see #addProperty(String, String)
511     */
512    public String removeProperty(String name)
513            throws IllegalActionException, NameDuplicationException {
514        String retval = (String) _propertyHash.remove(name);
515        if (retval != null) {
516            Attribute attribute = getAttribute("prop:" + name);
517            attribute.setContainer(null);
518        }
519        return retval;
520    }
521
522    /** Set the author.
523     *  @param author The author.
524     *  @see #getAuthor()
525     */
526    public void setAuthor(String author) {
527        _author = author;
528    }
529
530    /** Set the description.
531     *  @param description The description.
532     *  @see #getDescription()
533     */
534    public void setDescription(String description) {
535        _description = description;
536    }
537
538    /** Set the name of this docClass.
539     *  @param className The name of this docClass.
540     *  @see #getDocClass()
541     */
542    public void setDocClass(String className) {
543        _docClass = className;
544    }
545
546    /** Set the name of this document.
547     *  @param name The name of this document.
548     *  @see #getDocName()
549     */
550    public void setDocName(String name) {
551        _docName = name;
552    }
553
554    /** Set the port hash.
555     *  @param portHash The port hash.
556     *  @see #getPortHash()
557     */
558    public void setPortHash(Hashtable portHash) {
559        _portHash = portHash;
560    }
561
562    /** Set the property hashtable.
563     *  @param propertyHash The property hashtable.
564     *  @see #getPropertyHash()
565     *
566     *  FIXME: need to remove all existing ConfigurableAttributes
567     *  for properties and add new ones for new hash table.
568     */
569    public void setPropertyHash(Hashtable propertyHash) {
570        _propertyHash = propertyHash;
571    }
572
573    /** Set the user level documentation.
574     *  @param userLevelDocumentation The user level documentation.
575     *  @see #getUserLevelDocumentation()
576     */
577    public void setUserLevelDocumentation(String userLevelDocumentation) {
578        _userLevelDocumentation = userLevelDocumentation;
579    }
580
581    /** Set the version.
582     *  @param version The version.
583     *  @see #getVersion()
584     */
585    public void setVersion(String version) {
586        _version = version;
587    }
588
589    /**
590     * Exports this documentation attribute as docML.
591     * @return The docML
592     */
593    public String toDocML() {
594        createInstanceFromExisting(this);
595        StringBuffer results = new StringBuffer(
596                "<?xml version=\"1.0\" standalone=\"yes\"?>\n"
597                        + "<!DOCTYPE doc PUBLIC \"-//UC Berkeley//DTD DocML 1//EN\""
598                        + "\"http://ptolemy.eecs.berkeley.edu/xml/dtd/DocML_1.dtd\">\n"
599                        + "<doc name=\"" + _docName + "\" class=\"" + _docClass
600                        + "\">\n" + "<description>\n" + _userLevelDocumentation
601                        + "\n</description>\n" + "<author>" + _author
602                        + "</author>\n");
603
604        Enumeration ports = _portHash.keys();
605        while (ports.hasMoreElements()) {
606            String name = (String) ports.nextElement();
607            String desc = (String) _portHash.get(name);
608            results.append("<port name=\"" + name + "\">" + desc + "</port>\n");
609        }
610
611        Enumeration propItt = _propertyHash.keys();
612        while (propItt.hasMoreElements()) {
613            String name = (String) propItt.nextElement();
614            String desc = (String) _propertyHash.get(name);
615            results.append("<property name=\"" + name + "\">" + desc
616                    + "</property>\n");
617        }
618
619        results.append("</doc>\n");
620        return results.toString();
621    }
622
623    /**
624     * Method for configurable.
625     * In this class, we do nothing.
626     */
627    @Override
628    public void updateContent() throws InternalErrorException {
629        //do nothing
630    }
631
632    /** Update the documentation fields of this object from another
633     * KeplerDocumentationAttribute. A documentation field in
634     * this object is updated if it is empty and the corresponding field
635     * in the given object is not empty. However, if a field does not
636     * exist in this object, but is present in the given object, the
637     * field is *not* created in this object.
638     *
639     * @param oldDoc
640     *            The KeplerDocumentationAttribute from which to copy
641     *            attributes.
642     * @param printWhenReplacing
643     *            If true, print when the values are overwritten.
644     *
645     * @exception Exception
646     *             if there is an error updating the fields.
647     */
648    public void updateFromExisting(KeplerDocumentationAttribute oldDoc,
649            boolean printWhenReplacing) throws Exception {
650
651        if (oldDoc != null && oldDoc.attributeList() != null) {
652            Iterator<?> attributes = oldDoc.attributeList().iterator();
653            while (attributes.hasNext()) {
654                ConfigurableAttribute attribute = (ConfigurableAttribute) attributes
655                        .next();
656                String attributeName = attribute.getName();
657                boolean replaced = false;
658                if (attributeName.equals("description")) {
659                    String oldDescription = attribute.getConfigureText();
660                    if (_isEmpty(_description) && !_isEmpty(oldDescription)) {
661                        _description = oldDescription;
662                        replaced = true;
663                    }
664                } else if (attributeName.equals("author")) {
665                    String oldAuthor = attribute.getConfigureText();
666                    if (_isEmpty(_author) && !_isEmpty(oldAuthor)) {
667                        _author = oldAuthor;
668                        replaced = true;
669                    }
670                } else if (attributeName.equals("version")) {
671                    String oldVersion = attribute.getConfigureText();
672                    if (_isEmpty(_version) && !_isEmpty(oldVersion)) {
673                        _version = oldVersion;
674                        replaced = true;
675                    }
676                } else if (attributeName.equals("userLevelDocumentation")) {
677                    String oldUserLevelDocumentation = attribute
678                            .getConfigureText();
679                    if (_isEmpty(_userLevelDocumentation)
680                            && !_isEmpty(oldUserLevelDocumentation)) {
681                        _userLevelDocumentation = oldUserLevelDocumentation;
682                        replaced = true;
683                    }
684                } else if (attributeName.indexOf("port:") != -1) { // add to the
685                    // port hash
686                    String portName = attributeName.substring(
687                            attributeName.indexOf(":") + 1,
688                            attributeName.length());
689                    String portDescription = attribute.getConfigureText();
690                    if (portName != null) {
691                        if (portDescription == null) {
692                            portDescription = "";
693                        }
694                        String newPortDoc = (String) _portHash.get(portName);
695                        if (newPortDoc != null && _isEmpty(newPortDoc)
696                                && !_isEmpty(portDescription)) {
697                            // Attribute clonedAttribute = (Attribute)
698                            // attribute.clone();
699                            _portHash.put(portName, portDescription);
700                            replaced = true;
701                        }
702                    }
703
704                } else if (attributeName.indexOf("prop:") != -1) { // add to the
705                    // prop hash
706                    String propertyName = attributeName.substring(
707                            attributeName.indexOf(":") + 1,
708                            attributeName.length());
709                    String propertyDescription = attribute.getConfigureText();
710                    if (propertyName != null) {
711                        if (propertyDescription == null) {
712                            propertyDescription = "";
713                        }
714                        String newPropDoc = (String) _propertyHash
715                                .get(propertyName);
716                        if (newPropDoc != null && _isEmpty(newPropDoc)
717                                && !_isEmpty(propertyDescription)) {
718                            _propertyHash.put(propertyName,
719                                    propertyDescription);
720                            replaced = true;
721                        }
722                    }
723                }
724
725                if (replaced) {
726
727                    if (printWhenReplacing) {
728                        System.out.println(
729                                "WARNING: using old docs for " + attributeName
730                                        + " since the new ones appear empty.");
731                    }
732                    // Update the attribute too, creating one if it does not exist.
733                    ConfigurableAttribute myAttribute = (ConfigurableAttribute) getAttribute(
734                            attributeName);
735                    if (myAttribute == null) {
736                        myAttribute = new ConfigurableAttribute(this,
737                                attributeName);
738                    }
739                    myAttribute.configure(attribute.getBase(),
740                            attribute.getConfigureSource(),
741                            attribute.getConfigureText());
742                }
743            }
744        }
745    }
746
747    ///////////////////////////////////////////////////////////////////
748    ////                         private methods                   ////
749
750    /** A utility method to determine if a field's string is empty.
751     * @return Returns true if the field is null or the content appears to be
752     *         empty.
753     */
754    private static boolean _isEmpty(String string) {
755
756        if (string == null) {
757            return true;
758        }
759        String trimmed = string.trim();
760        if (trimmed.isEmpty() || trimmed.equalsIgnoreCase("null")) {
761            return true;
762        }
763
764        return false;
765    }
766
767    ///////////////////////////////////////////////////////////////////
768    ////                         private fields                    ////
769
770    //members for Configurable
771    private String source;
772
773    private String text;
774
775    //members for DocumenationAttribute
776    private String _docName;
777
778    private String _docClass;
779
780    private String _description;
781
782    private String _author;
783
784    private String _version;
785
786    private String _userLevelDocumentation;
787
788    private Hashtable _portHash = new Hashtable();
789
790    private Hashtable _propertyHash = new Hashtable();
791}