1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.oracle.javafx.scenebuilder.kit.library.util; 33 34 import java.io.ByteArrayInputStream; 35 import java.io.IOException; 36 import java.nio.charset.Charset; 37 import java.nio.file.Path; 38 import java.util.Enumeration; 39 import java.util.jar.JarEntry; 40 import java.util.jar.JarFile; 41 import javafx.fxml.FXMLLoader; 42 43 /** 44 * 45 * 46 */ 47 public class JarExplorer { 48 49 private final Path jar; 50 51 public JarExplorer(Path jar) { 52 assert jar != null; 53 assert jar.isAbsolute(); 54 55 this.jar = jar; 56 } 57 58 public JarReport explore(ClassLoader classLoader) throws IOException { 59 final JarReport result = new JarReport(jar); 60 61 try (JarFile jarFile = new JarFile(jar.toFile())) { 62 final Enumeration<JarEntry> e = jarFile.entries(); 63 while (e.hasMoreElements()) { 64 final JarEntry entry = e.nextElement(); 65 result.getEntries().add(exploreEntry(entry, classLoader)); 66 } 67 } 68 69 return result; 70 } 71 72 public static String makeFxmlText(Class<?> klass) { 73 final StringBuilder result = new StringBuilder(); 74 75 /* 76 * <?xml version="1.0" encoding="UTF-8"?> //NOI18N 77 * 78 * <?import a.b.C?> 79 * 80 * <C/> 81 */ 82 83 result.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); //NOI18N 84 85 result.append("<?import "); //NOI18N 86 result.append(klass.getCanonicalName()); 87 result.append("?>"); //NOI18N 88 result.append("<"); //NOI18N 89 result.append(klass.getSimpleName()); 90 result.append("/>\n"); //NOI18N 91 92 return result.toString(); 93 } 94 95 96 public static Object instantiateWithFXMLLoader(Class<?> klass, ClassLoader classLoader) throws IOException { 97 Object result; 98 99 final String fxmlText = makeFxmlText(klass); 100 final byte[] fxmlBytes = fxmlText.getBytes(Charset.forName("UTF-8")); //NOI18N 101 102 final FXMLLoader fxmlLoader = new FXMLLoader(); 103 try { 104 fxmlLoader.setClassLoader(classLoader); 105 result = fxmlLoader.load(new ByteArrayInputStream(fxmlBytes)); 106 } catch(IOException x) { 107 throw x; 108 } catch(RuntimeException|Error x) { 109 throw new IOException(x); 110 } 111 112 return result; 113 } 114 115 /* 116 * Private 117 */ 118 119 private JarReportEntry exploreEntry(JarEntry entry, ClassLoader classLoader) { 120 JarReportEntry.Status status; 121 Throwable entryException; 122 Class<?> entryClass; 123 124 if (entry.isDirectory()) { 125 status = JarReportEntry.Status.IGNORED; 126 entryClass = null; 127 entryException = null; 128 } else { 129 final String className = makeClassName(entry.getName()); 130 // Filtering out what starts with com.javafx. is bound to DTL-6378. 131 if (className == null || className.startsWith("java.") //NOI18N 132 || className.startsWith("javax.") || className.startsWith("javafx.") //NOI18N 133 || className.startsWith("com.oracle.javafx.scenebuilder.") //NOI18N 134 || className.startsWith("com.javafx.")) { //NOI18N 135 status = JarReportEntry.Status.IGNORED; 136 entryClass = null; 137 entryException = null; 138 } else { 139 try { 140 // Some reading explaining why using Class.forName is not appropriate: 141 // http://blog.osgi.org/2011/05/what-you-should-know-about-class.html 142 // http://blog.bjhargrave.com/2007/09/classforname-caches-defined-class-in.html 143 // http://stackoverflow.com/questions/8100376/class-forname-vs-classloader-loadclass-which-to-use-for-dynamic-loading 144 entryClass = classLoader.loadClass(className); // Note: static intializers of entryClass are not run, this doesn't seem to be an issue 145 try { 146 instantiateWithFXMLLoader(entryClass, classLoader); 147 status = JarReportEntry.Status.OK; 148 entryException = null; 149 } catch(RuntimeException|IOException x) { 150 status = JarReportEntry.Status.CANNOT_INSTANTIATE; 151 entryException = x; 152 } 153 } catch(Error | ClassNotFoundException x) { 154 status = JarReportEntry.Status.CANNOT_LOAD; 155 entryClass = null; 156 entryException = x; 157 } 158 } 159 } 160 161 return new JarReportEntry(entry.getName(), status, entryException, entryClass); 162 } 163 164 165 private String makeClassName(String entryName) { 166 final String result; 167 168 if (entryName.endsWith(".class") == false) { //NOI18N 169 result = null; 170 } else if (entryName.contains("$")) { //NOI18N 171 // We skip inner classes for now 172 result = null; 173 } else { 174 final int endIndex = entryName.length()-6; // ".class" -> 6 //NOI18N 175 result = entryName.substring(0, endIndex).replace("/", "."); //NOI18N 176 } 177 178 return result; 179 } 180 }