001/*
002 * An attribute that contains a model described in MoML.
003 *
004 * Copyright (c) 2008-2014 The Regents of the University of California. All
005 * rights reserved. Permission is hereby granted, without written agreement and
006 * without 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 of
009 * this software.
010 *
011 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
012 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
013 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
014 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
015 *
016 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
017 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
018 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
019 * "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE
020 * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
021 *
022 * PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY
023 *
024 */
025package ptolemy.moml;
026
027import java.io.IOException;
028import java.io.StringReader;
029import java.io.Writer;
030import java.net.MalformedURLException;
031import java.net.URI;
032import java.net.URL;
033
034import ptolemy.data.expr.FileParameter;
035import ptolemy.kernel.attributes.URIAttribute;
036import ptolemy.kernel.util.Attribute;
037import ptolemy.kernel.util.Configurable;
038import ptolemy.kernel.util.IllegalActionException;
039import ptolemy.kernel.util.NameDuplicationException;
040import ptolemy.kernel.util.NamedObj;
041import ptolemy.kernel.util.Workspace;
042import ptolemy.util.FileUtilities;
043
044///////////////////////////////////////////////////////////////////
045//// MoMLModelAttribute
046
047/**
048 * An attribute that has a model described in MoML.
049 * The MoML is specified by calling {@link #configure(URL, String, String)},
050 * or by including the MoML within <configure> tags in a MoML file.
051 * The MoML is returned by the {@link #getConfigureText()} method.
052 * The {@link #getContainedModel()} method returns the model specified
053 * by the MoML.
054 * <p>
055 * When an instance of this attribute is exported to MoML, the MoML
056 * description above will be included in the exported MoML within
057 * &lt;configure&gt; tags.
058 * <p>
059 * An instance of this attribute may wish to override the default
060 * "Look Inside" behavior by including an instance of
061 * ptolemy.vergil.toolbox.MoMLModelAttributeControllerFactory
062 * as an attribute contained by this instance.  Instead of
063 * having an explicit compile-time dependency between this class and
064 * MoMLModelAttributeControllerFactory, derived classes should use MoML
065 * to set up the containment relationship.  For example,
066 * <pre>
067 * &lt;property name="MyAttribute" class="ptolemy.moml.MoMLModelAttribute"&gt;
068 *     &lt;property name="_controllerFactory" class="ptolemy.vergil.toolbox.MoMLModelAttributeControllerFactory"&gt;
069 *     &lt;/property&gt;
070 *     &lt;configure&gt;
071 *        ... my MoML text here ...
072 *     &lt;/configure&gt;
073 * &lt;/property&gt;
074 * </pre>
075 *
076 * @author Dai Bui, Edward Lee, Ben Lickly, Charles Shelton
077 * @version $Id$
078 * @since Ptolemy II 8.0
079 * @Pt.ProposedRating Red (tfeng)
080 * @Pt.AcceptedRating Red (tfeng)
081 */
082public class MoMLModelAttribute extends Attribute implements Configurable {
083
084    /** Create a model attribute with the specified container and name.
085     *  @param container The specified container.
086     *  @param name The specified name.
087     *  @exception IllegalActionException If the attribute is not of an
088     *   acceptable class for the container, or if the name contains a period.
089     *  @exception NameDuplicationException If the name coincides with an
090     *   attribute already in the container.
091     */
092    public MoMLModelAttribute(NamedObj container, String name)
093            throws NameDuplicationException, IllegalActionException {
094        super(container, name);
095
096        modelURL = new FileParameter(this, "modelURL");
097    }
098
099    ///////////////////////////////////////////////////////////////////
100    ////                         parameters                        ////
101
102    /** URL from which to get the model. If this is specified,
103     *  then the URL will be referenced in the exported configure tag
104     *  rather than including the MoML for the model in the configure
105     *  tag. This parameter is a string that defaults to empty. This string
106     *  can either be an absolute, fully-qualified URL, or a URL relative
107     *  to the container model's file location.  A URL relative to the
108     *  system's classpath can also be specified by a string starting with
109     *  <code>$CLASSPATH/{relative URL}</code>.
110     */
111    public FileParameter modelURL;
112
113    ///////////////////////////////////////////////////////////////////
114    ////                         public methods                    ////
115
116    /** React to a change in an attribute. If the modelURL attribute
117     *  changes, reconfigure the MoML model with the new URL string.
118     *  @param attribute The attribute that changed.
119     *  @exception IllegalActionException Thrown if the URL string contained in the
120     *   modelURL attribute is not valid.
121     */
122    @Override
123    public void attributeChanged(Attribute attribute)
124            throws IllegalActionException {
125
126        if (attribute.equals(modelURL)) {
127            if (modelURL != null && !modelURL.stringValue().equals("")) {
128                String modelURLString = modelURL.stringValue();
129
130                // The modelURLString could be either an absolute URL string
131                // or a file location relative to the container model file location.
132                // If it is the latter, we need to create an absolute URL string.
133                modelURLString = _createAbsoluteModelURLString(modelURLString);
134
135                try {
136                    configure(null, modelURLString, null);
137                } catch (Exception ex) {
138                    throw new IllegalActionException(this, ex,
139                            "Could not "
140                                    + "configure the model contents of the "
141                                    + "MoMLModelAttribute with the given URL.");
142                }
143            }
144        } else {
145            super.attributeChanged(attribute);
146        }
147    }
148
149    /** Return a clone of this model attribute. This also creates a clone for the
150     *  contained model.
151     *  @param workspace The workspace for the cloned object.
152     *  @return A clone.
153     *  @exception CloneNotSupportedException Thrown if an error occurs while
154     *   cloning the attribute or the contained model.
155     */
156    @Override
157    public Object clone(Workspace workspace) throws CloneNotSupportedException {
158        MoMLModelAttribute newObject = (MoMLModelAttribute) super.clone(
159                workspace);
160        if (_model != null) {
161            newObject._model = (NamedObj) _model.clone(workspace());
162        }
163        return newObject;
164    }
165
166    /** Construct and configure the contained model with the specified source and
167     *  text. This parses the specified MoML text.
168     *  @param base The base URL for relative references, or null if not known.
169     *  @param source The URI of a document providing source, which if specified,
170     *   will be used to obtain the text. In that case, the text argument will be
171     *   ignored.
172     *  @param text The MoML description.
173     *  @exception Exception If the parsing fails.
174     */
175    @Override
176    public void configure(URL base, String source, String text)
177            throws Exception {
178        _source = null;
179        MoMLParser parser = new MoMLParser(workspace());
180
181        if (source != null && !source.trim().equals("")) {
182            _source = source;
183            _model = parser.parse(base, new URL(source));
184        } else if (text != null && !text.trim().equals("")) {
185            _model = parser.parse(base, null, new StringReader(text));
186        } else {
187            // source and text are null.
188            _model = null;
189        }
190    }
191
192    /** Return the input source that was specified the last time the configure
193     *  method was called.
194     *  @return The string representation of the input URL, or null if the
195     *  no source has been used to configure this object, or null if no
196     *  external source need be used to configure this object.
197     */
198    @Override
199    public String getConfigureSource() {
200        return _source;
201    }
202
203    /** Return the MoML description of the model, if there is one, and
204     *  null otherwise.
205     *  @return The text to include in a configure tag.
206     */
207    @Override
208    public String getConfigureText() {
209        // If the source is not null, there is no need for configure text,
210        // so return null. Otherwise return the model MoML text.
211        if (_source != null) {
212            return null;
213        } else if (_model != null) {
214            return _model.exportMoML();
215        }
216
217        return null;
218    }
219
220    /** Return the contained model.
221     *  @return The contained model.
222     */
223    public NamedObj getContainedModel() {
224        return _model;
225    }
226
227    ///////////////////////////////////////////////////////////////////
228    ////                         protected methods                 ////
229
230    /** Write a MoML description this object, which includes a
231     *  MoML description of the contained model within the &lt;configure&gt; tag.
232     *  If the source URL is specified, do not write the MoML description.
233     *  @param output The output stream to write to.
234     *  @param depth The depth in the hierarchy, to determine indenting.
235     *  @exception IOException If an I/O error occurs.
236     */
237    @Override
238    protected void _exportMoMLContents(Writer output, int depth)
239            throws IOException {
240        super._exportMoMLContents(output, depth);
241        if (_source == null && _model != null) {
242            output.write(_getIndentPrefix(depth) + "<configure>\n");
243            _model.exportMoML(output, depth + 1);
244            output.write(_getIndentPrefix(depth) + "</configure>\n");
245        }
246    }
247
248    ///////////////////////////////////////////////////////////////////
249    ////                         protected variables               ////
250
251    /** The contained model. This is protected so that derived classes
252     *  can provide a default model.
253     */
254    protected NamedObj _model;
255
256    ///////////////////////////////////////////////////////////////////
257    ////                         private methods                   ////
258
259    /** Return a string representing the full absolute URL of the contained model.
260     *  If the input string is already an absolute URL, just return it.
261     *  If the input string is a file path location relative to the model's
262     *  directory, then return the full URL string by constructing the full
263     *  URL from the base model path and the given relative path.
264     *
265     *  @param modelURLString The model URL string specified by the modelURL parameter.
266     *  @return A string representing the full absolute URL for the model URL.
267     *  @exception IllegalActionException Thrown if the model URL string is invalid.
268     */
269    private String _createAbsoluteModelURLString(String modelURLString)
270            throws IllegalActionException {
271        try {
272            // If the given string is a correctly formed URL, this constructor
273            // will not throw an exception.
274            new URL(modelURLString);
275            return modelURLString;
276        } catch (MalformedURLException e) {
277            try {
278                URI baseModelURI = URIAttribute.getModelURI(this);
279                URL modelURLObject = FileUtilities.nameToURL(modelURLString,
280                        baseModelURI, null);
281                return modelURLObject.toString();
282            } catch (IOException ioe) {
283                throw new IllegalActionException(this, ioe,
284                        "Invalid MoMLModelAttribute model URL: "
285                                + modelURLString);
286            }
287        }
288    }
289
290    ///////////////////////////////////////////////////////////////////
291    ////                         private variables                 ////
292
293    /** The source specified by the last call to configure(). */
294    private String _source;
295}