001/* An object that can create a new Effigy
002
003 Copyright (c) 1997-2015 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.actor.gui;
030
031import java.io.BufferedReader;
032import java.io.IOException;
033import java.io.InputStream;
034import java.io.InputStreamReader;
035import java.net.URL;
036import java.security.AccessControlException;
037import java.util.Iterator;
038
039import ptolemy.kernel.CompositeEntity;
040import ptolemy.kernel.util.IllegalActionException;
041import ptolemy.kernel.util.NameDuplicationException;
042import ptolemy.kernel.util.Workspace;
043import ptolemy.util.ClassUtilities;
044
045///////////////////////////////////////////////////////////////////
046//// EffigyFactory
047
048/**
049 A configuration contains an instance of this class, and uses it to create
050 effigies from a URL, or to create blank effigies of a particular kind.
051 This base class assumes that it contains other effigy factories.
052 Its createEffigy() methods defer to each contained factory in order
053 until one is capable of creating an effigy. Subclasses of this class
054 will usually be inner classes of an Effigy and will create the Effigy,
055 or they might themselves be aggregates of instances of EffigyFactory.
056
057 @author Steve Neuendorffer and Edward A. Lee
058 @version $Id$
059 @since Ptolemy II 1.0
060 @Pt.ProposedRating Yellow (eal)
061 @Pt.AcceptedRating Red (cxh)
062 @see Configuration
063 @see Effigy
064 */
065public class EffigyFactory extends CompositeEntity {
066    /** Create a factory in the specified workspace.
067     *  @param workspace The workspace.
068     */
069    public EffigyFactory(Workspace workspace) {
070        super(workspace);
071    }
072
073    /** Create a factory with the given name and container.
074     *  @param container The container.
075     *  @param name The name.
076     *  @exception IllegalActionException If the container is incompatible
077     *   with this entity.
078     *  @exception NameDuplicationException If the name coincides with
079     *   an entity already in the container.
080     */
081    public EffigyFactory(CompositeEntity container, String name)
082            throws IllegalActionException, NameDuplicationException {
083        super(container, name);
084    }
085
086    ///////////////////////////////////////////////////////////////////
087    ////                         public methods                    ////
088
089    /** Return true if this effigy factory is capable of creating
090     *  an effigy without a URL being specified.  That is, it is capable
091     *  of creating a blank effigy with no model data.
092     *  In this base class, this method returns true if at least one
093     *  contained effigy factory returns true.
094     *  @return True if this factory can create a blank effigy.
095     */
096    public boolean canCreateBlankEffigy() {
097        Iterator factories = entityList(EffigyFactory.class).iterator();
098
099        while (factories.hasNext()) {
100            EffigyFactory factory = (EffigyFactory) factories.next();
101
102            if (factory.canCreateBlankEffigy()) {
103                return true;
104            }
105        }
106
107        return false;
108    }
109
110    /** Check the URL input for a DTD.  Only the first 5 lines are read
111     *  from the URL.  Any text that matches <code>&lt;?xml.*?&gt;</code>
112     *  is removed before checking.
113     *  @param input The DTD to check.
114     *  @param dtdStart The start of the DTD, typically "&lt;!DOCTYPE".
115     *  @param dtdEndRegExp The optional ending regular expression.  If
116     *  this parameter is null, then it is ignored.
117     *  @return True if the input starts with dtdStart and, if dtdEndRegExp
118     *  is non-null, ends with dtdEndRegExp.
119     *  @exception IOException if there is a problem opening or reading
120     *  the input.
121     */
122    public static boolean checkForDTD(URL input, String dtdStart,
123            String dtdEndRegExp) throws IOException {
124        // This method is a convenience method used to avoid code duplication.
125        InputStream stream = null;
126        try {
127            stream = input.openStream();
128        } catch (AccessControlException ex) {
129            // Applets will throw this.
130            AccessControlException exception = new AccessControlException(
131                    "Failed to open \"" + input + "\"");
132            exception.initCause(ex);
133            throw exception;
134        } catch (IOException ex) {
135
136            // If we are running under Web Start, we
137            // might have a URL that refers to another
138            // jar file.
139            URL anotherURL = ClassUtilities
140                    .jarURLEntryResource(input.toExternalForm());
141            if (anotherURL == null) {
142                throw ex;
143
144            }
145            stream = anotherURL.openStream();
146        }
147
148        boolean foundDTD = false;
149        BufferedReader reader = null;
150        try {
151            reader = new BufferedReader(new InputStreamReader(stream,
152                    java.nio.charset.Charset.defaultCharset()));
153            int lineCount = 0;
154            while (lineCount < 5) {
155                String contents = reader.readLine();
156                lineCount++;
157                if (contents == null) {
158                    break;
159                }
160                // Change from Ian Brown to handle XSLT where after using XSLT,
161                // lines looked like:
162                // <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE entity PUBLIC "-//UC Berkeley//DTD MoML 1//EN"
163
164                contents = contents.replaceFirst("<\\?xml.*\\?>", "");
165                if (dtdEndRegExp != null) {
166                    if (contents.startsWith(dtdStart)
167                            && contents.matches(dtdEndRegExp)) {
168                        // This file has the DTD for which we are looking.
169                        foundDTD = true;
170                        break;
171                    } else {
172                        // Test if DTD public Id is declared on the next line
173                        if (contents.startsWith(dtdStart)) {
174                            contents += reader.readLine();
175                            if (contents.matches(dtdEndRegExp)) {
176                                // This file has the DTD for which we are looking.
177                                foundDTD = true;
178                                break;
179                            }
180                        }
181                    }
182                } else if (contents.startsWith(dtdStart)) {
183                    // dtdEndRegExp is null so we don't check it
184                    foundDTD = true;
185                    break;
186                }
187            }
188        } finally {
189            if (reader != null) {
190                reader.close();
191            }
192        }
193        return foundDTD;
194    }
195
196    /** Create a new blank effigy in the given container. This base class
197     *  defers to each contained effigy factory until one returns
198     *  an effigy.  If there are no contained effigies, or if none
199     *  returns an effigy, then this method returns null. Subclasses will
200     *  override this method to create an effigy of an appropriate type.
201     *  @param container The container for the effigy.
202     *  @return A new effigy.
203     *  @exception Exception If the effigy created by one of the contained
204     *   factories is incompatible with the specified container, or a name
205     *   duplication occurs.
206     */
207    public Effigy createEffigy(CompositeEntity container) throws Exception {
208        return createEffigy(container, null, null);
209    }
210
211    /** Create a new effigy in the given container by reading the specified
212     *  URL. If the specified URL is null, then create a blank effigy.
213     *  The specified base is used to expand any relative file references
214     *  within the URL.  This base class defers to each contained effigy
215     *  factory until one returns an effigy.  If there are no
216     *  contained effigies, or if none
217     *  returns an effigy, then this method returns null. Subclasses will
218     *  override this method to create an effigy of an appropriate type.
219     *  @param container The container for the effigy.
220     *  @param base The base for relative file references, or null if
221     *   there are no relative file references.
222     *  @param in The input URL.
223     *  @return A new effigy.
224     *  @exception Exception If the stream cannot be read, or if the data
225     *   is malformed in some way.
226     */
227    public Effigy createEffigy(CompositeEntity container, URL base, URL in)
228            throws Exception {
229        Effigy effigy = null;
230        Iterator factories = entityList(EffigyFactory.class).iterator();
231
232        while (factories.hasNext() && effigy == null) {
233            EffigyFactory factory = (EffigyFactory) factories.next();
234            effigy = factory.createEffigy(container, base, in);
235        }
236
237        return effigy;
238    }
239
240    /** Return the extension on the name of the specified URL.
241     *  This is a utility method designed to help derived classes
242     *  decide whether the URL matches the particular type of effigy
243     *  they can create.  If the URL has no extension, return an
244     *  empty string.
245     *  @param url A URL.
246     *  @return The extension on the URL.
247     */
248    public static String getExtension(URL url) {
249        String filename = url.getFile();
250        int dotIndex = filename.lastIndexOf(".");
251
252        if (dotIndex < 0) {
253            return "";
254        }
255
256        try {
257            int slashIndex = filename.lastIndexOf("/");
258            if (slashIndex > 0 && slashIndex > dotIndex) {
259                // The last / is after the last .
260                // for example foo.bar/bif
261                return "";
262            }
263            return filename.substring(dotIndex + 1);
264        } catch (IndexOutOfBoundsException ex) {
265            return "";
266        }
267    }
268}