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><?xml.*?></code> 112 * is removed before checking. 113 * @param input The DTD to check. 114 * @param dtdStart The start of the DTD, typically "<!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}