001/* A tableau representing an HTML window. 002 003 Copyright (c) 2000-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 */ 027package ptolemy.actor.gui; 028 029import java.io.IOException; 030import java.net.MalformedURLException; 031import java.net.URI; 032import java.net.URISyntaxException; 033import java.net.URL; 034 035import ptolemy.kernel.util.Attribute; 036import ptolemy.kernel.util.IllegalActionException; 037import ptolemy.kernel.util.NameDuplicationException; 038import ptolemy.kernel.util.NamedObj; 039import ptolemy.kernel.util.Settable; 040import ptolemy.kernel.util.StringAttribute; 041import ptolemy.util.ClassUtilities; 042import ptolemy.util.FileUtilities; 043import ptolemy.util.StringUtilities; 044 045/////////////////////////////////////////////////////////////////// 046//// HTMLViewerTableau 047 048/** 049 A tableau representing a rendered HTML view in a toplevel window. 050 The URL that is viewed is given by the <i>url</i> parameter, and 051 can be either an absolute URL, a system fileName, or a resource that 052 can be loaded relative to the classpath. For more information about how 053 the URL is specified, see MoMLApplication.specToURL(). 054 <p> 055 The constructor of this 056 class creates the window. The text window itself is an instance 057 of HTMLViewer, and can be accessed using the getFrame() method. 058 As with other tableaux, this is an entity that is contained by 059 an effigy of a model. 060 There can be any number of instances of this class in an effigy. 061 062 @author Steve Neuendorffer and Edward A. Lee 063 @version $Id$ 064 @since Ptolemy II 1.0 065 @Pt.ProposedRating Yellow (eal) 066 @Pt.AcceptedRating Red (cxh) 067 @see Effigy 068 @see HTMLViewer 069 @see MoMLApplication#specToURL(String) 070 */ 071public class HTMLViewerTableau extends Tableau { 072 /** Construct a new tableau for the model represented by the given effigy. 073 * This creates an instance of HTMLViewer. It does not make the frame 074 * visible. To do that, call show(). 075 * @param container The container. 076 * @param name The name. 077 * @exception IllegalActionException If the container does not accept 078 * this entity (this should not occur). 079 * @exception NameDuplicationException If the name coincides with an 080 * attribute already in the container. 081 */ 082 public HTMLViewerTableau(Effigy container, String name) 083 throws IllegalActionException, NameDuplicationException { 084 super(container, name); 085 086 url = new StringAttribute(this, "url"); 087 // Set the tableau so that we can get the Configuration and Kepler icon. 088 HTMLViewer frame = new HTMLViewer(this); 089 setFrame(frame); 090 frame.setTableau(this); 091 092 } 093 094 /////////////////////////////////////////////////////////////////// 095 //// public parameters //// 096 097 /** The URL to display. */ 098 public StringAttribute url; 099 100 /////////////////////////////////////////////////////////////////// 101 //// public methods //// 102 103 /** If the argument is the <i>url</i> parameter, then open the 104 * specified URL and display its contents. 105 * @param attribute The attribute that changed. 106 * @exception IllegalActionException If the URL cannot be opened, 107 * or if the base class throws it. 108 */ 109 @Override 110 public void attributeChanged(Attribute attribute) 111 throws IllegalActionException { 112 if (attribute == url) { 113 String urlSpec = ((Settable) attribute).getExpression(); 114 115 try { 116 // NOTE: This cannot handle a URL that is relative to the 117 // MoML file within which this attribute might be being 118 // defined. Is there any way to do that? 119 URL toRead = ConfigurationApplication.specToURL(urlSpec); 120 ((HTMLViewer) getFrame()).setPage(toRead); 121 } catch (IOException ex) { 122 throw new IllegalActionException(this, ex, 123 "Cannot open URL: " + urlSpec); 124 } 125 } else { 126 super.attributeChanged(attribute); 127 } 128 } 129 130 /////////////////////////////////////////////////////////////////// 131 //// inner classes //// 132 133 /** A factory that creates HTML viewer tableaux for Ptolemy models. 134 */ 135 public static class Factory extends TableauFactory { 136 /** Create a factory with the given name and container. 137 * @param container The container. 138 * @param name The name. 139 * @exception IllegalActionException If the container is incompatible 140 * with this attribute. 141 * @exception NameDuplicationException If the name coincides with 142 * an attribute already in the container. 143 */ 144 public Factory(NamedObj container, String name) 145 throws IllegalActionException, NameDuplicationException { 146 super(container, name); 147 } 148 149 /////////////////////////////////////////////////////////////////// 150 //// public methods //// 151 152 /** If the specified effigy already contains a tableau named 153 * "htmlTableau", then return that tableau; otherwise, create 154 * a new instance of HTMLViewerTableau in the specified 155 * effigy, and name it "htmlTableau". If the specified 156 * effigy is not an instance of HTMLEffigy, then do not 157 * create a tableau and return null. It is the 158 * responsibility of callers of this method to check the 159 * return value and call show(). 160 * <p>If the URL contains $CLASSPATH, then we look in the 161 * classpath for the URL. 162 * @param effigy The effigy. 163 * @return A HTML viewer tableau, or null if one cannot be 164 * found or created. 165 * @exception Exception If the factory should be able to create a 166 * tableau for the effigy, but something goes wrong. 167 */ 168 @Override 169 public Tableau createTableau(Effigy effigy) throws Exception { 170 if (effigy instanceof HTMLEffigy) { 171 // Indicate to the effigy that this factory contains effigies 172 // offering multiple views of the effigy data. 173 effigy.setTableauFactory(this); 174 175 // First see whether the effigy already contains an 176 // HTMLViewerTableau. 177 HTMLViewerTableau tableau = (HTMLViewerTableau) effigy 178 .getEntity("htmlTableau"); 179 180 if (tableau == null) { 181 tableau = new HTMLViewerTableau(effigy, "htmlTableau"); 182 } 183 184 // Unfortunately, if we have a jar url, (for example 185 // jar:file:/C:/foo.jar!/intro.htm 186 // then the java.net.URI toURL() method will return 187 // a URL like jar:, which is missing the file: part 188 // This breaks Ptolemy II under WebStart. 189 URL pageURL = new URL(effigy.uri.getURI().toString()); 190 191 try { 192 ((HTMLViewer) tableau.getFrame()).setPage(pageURL); 193 } catch (IOException io) { 194 // setPage() throws an IOException if the page can't 195 // be found. If we are under Web Start, it could be 196 // that we are looking in the wrong Jar file, so 197 // we try again. 198 String urlString = effigy.uri.getURI().toString(); 199 URL anotherURL = ClassUtilities 200 .jarURLEntryResource(urlString); 201 202 if (anotherURL == null && urlString.indexOf("#") != -1) { 203 anotherURL = _entryResourceWithoutFragment(urlString); 204 } 205 206 if (anotherURL == null) { 207 try { 208 // Search relative to to $PTII in a jar URL 209 anotherURL = _absolutePTIIURLToJarURL(urlString); 210 } catch (Throwable throwable) { 211 // Ignore: failed 212 } 213 } 214 215 if (anotherURL == null 216 && urlString.indexOf("$CLASSPATH") != -1) { 217 // The URL contains $CLASSPATH 218 String classpathString = urlString 219 .substring(urlString.indexOf("$CLASSPATH")); 220 anotherURL = FileUtilities.nameToURL(classpathString, 221 null, null); 222 } 223 224 if (anotherURL == null) { 225 IOException io2 = new IOException("---"); 226 io2.initCause(io); 227 throw io2; 228 } 229 230 // Try to set the title bar? 231 try { 232 effigy.uri.setURI(new URI(anotherURL.toString())); 233 tableau.setTitle(anotherURL.toString()); 234 } catch (Exception ex) { 235 try { 236 // URI's can't deal with spaces, so we 237 // convert to %20 238 URL canonicalizedURL = JNLPUtilities 239 .canonicalizeJarURL(anotherURL); 240 effigy.uri.setURI( 241 new URI(canonicalizedURL.toString())); 242 tableau.setTitle(canonicalizedURL.toString()); 243 } catch (Throwable ex2) { 244 throw ex; 245 } 246 } 247 248 ((HTMLViewer) tableau.getFrame()).setPage(anotherURL); 249 } 250 251 // Don't call show() here. If show() is called here, 252 // then you can't set the size of the window after 253 // createTableau() returns. This will affect how 254 // centering works. 255 return tableau; 256 } else { 257 return null; 258 } 259 } 260 } 261 262 /////////////////////////////////////////////////////////////////// 263 //// private methods //// 264 265 /** If possible convert an absolute URL that refers to a file inside 266 * the $PTII tree to a jar URL. 267 * <p>For example, if doc/codeDoc.jar is in the classpath, but 268 * the contents of codeDoc/ do not exist as files, then calling 269 * this method with: 270 * file:/C:/ptII/doc/codeDoc/ptolemy/util/package-summary.html#package_description] 271 * will return: 272 * jar:file:/C:/cxh/ptII/doc/codeDoc.jar!/doc/codeDoc/ptolemy/kernel/package-summary.html#package_description 273 274 * 275 * @param urlName The absolute URL to be converted. 276 * @return The jar url that refers to a file if the file can be found 277 * as a resource or null if the file cannot be found. 278 * @exception URISyntaxException If there are problems creating a URI. 279 * @exception MalformedURLException If there are problems creating a URL. 280 */ 281 public static URL _absolutePTIIURLToJarURL(String urlName) 282 throws java.net.URISyntaxException, java.net.MalformedURLException { 283 // Try looking up the URL as a resource relative to $PTII. 284 String ptIIDirAsURLName = StringUtilities 285 .getProperty("ptolemy.ptII.dirAsURL"); 286 287 // FIXME: This is an ugly hack. 288 // If the user has a Windows installation that includes the 289 // source jar file, then when they open whatsNew4.0.htm 290 // and click on a javadoc link that is in codeDoc.jar but 291 // not a separate file, then the file will come up missing 292 // because ptolemy.ptII.dirAsURL refers to ptsupport.jar 293 // The hack is to strip that out. 294 String ptsupportPath = "/ptolemy/ptsupport.jar"; 295 296 if (ptIIDirAsURLName.endsWith(ptsupportPath)) { 297 ptIIDirAsURLName = ptIIDirAsURLName.substring(0, 298 ptIIDirAsURLName.length() - ptsupportPath.length()); 299 } 300 301 String relativePath = null; 302 303 if (urlName.startsWith(ptIIDirAsURLName)) { 304 relativePath = urlName.substring(ptIIDirAsURLName.length()); 305 } else { 306 // If we click on a link, it might be: 307 // "file:/C:/ptII/doc/codeDoc/" 308 // but ptolemy.ptII.dirAsURL might be 309 // "file:/c:/ptII" 310 // URL.sameFile() will not work here, so we use URI.relativize() 311 URI uri = new URI(urlName); 312 URI ptIIDirAsURI; 313 314 try { 315 ptIIDirAsURI = new URI(ptIIDirAsURLName); 316 } catch (java.net.URISyntaxException ex) { 317 // If the ptIIDirAsURLName has a space in it, then it is 318 // not a legitimate URI, so we substitute in %20 319 ptIIDirAsURI = new URI(StringUtilities 320 .substitute(ptIIDirAsURLName, " ", "%20")); 321 } 322 323 URI relativeURI = uri.relativize(ptIIDirAsURI); 324 325 if (relativeURI.toURL().sameFile(ptIIDirAsURI.toURL())) { 326 int offset = 0; 327 328 if (urlName.startsWith("jar:")) { 329 offset = 4; 330 } 331 332 // Hmm, should this be 333 relativePath = uri.toString() 334 .substring(ptIIDirAsURI.toString().length() + offset); 335 336 //relativePath = urlName.substring(ptIIDirAsURLName.length()); 337 } 338 } 339 340 if (relativePath == null) { 341 return null; 342 } else { 343 if (relativePath.startsWith("/")) { 344 relativePath = relativePath.substring(1); 345 } 346 347 URL anotherURL = ClassUtilities.getResource(relativePath); 348 349 if (anotherURL == null && relativePath.indexOf('#') != -1) { 350 // getResource does not work on paths that look like: 351 // "package-summary.html#package_description" 352 // So, we get the resource without the 353 // trailing # and then append it 354 try { 355 anotherURL = _entryResourceWithoutFragment(relativePath); 356 } catch (IOException ex) { 357 // Ignored 358 } 359 } 360 361 return anotherURL; 362 } 363 } 364 365 // Given a string that contains a URL that has a # character signifiying 366 // a fragment, strip the fragment off and look up the URL as a resource. 367 // getResource() does not work on paths that look like: 368 // "package-summary.html#package_description" 369 // So, we get the resource without the 370 // trailing # and then append it. If the resource cannot be found, 371 // we return null 372 // @param urlString A string representing a jar URL or a relative URL. 373 private static URL _entryResourceWithoutFragment(String urlString) 374 throws IOException, MalformedURLException { 375 String urlStringBase = urlString.substring(0, 376 urlString.lastIndexOf("#")); 377 378 URL anotherURL = null; 379 380 if (urlStringBase.startsWith("jar:")) { 381 anotherURL = ClassUtilities.jarURLEntryResource(urlStringBase); 382 } else { 383 anotherURL = ClassUtilities.getResource(urlStringBase); 384 } 385 386 if (anotherURL != null) { 387 anotherURL = new URL(anotherURL.toString() 388 + urlString.substring(urlString.lastIndexOf("#"))); 389 } 390 391 return anotherURL; 392 } 393}