1 /* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package jaxp.library; 24 25 import static org.testng.Assert.fail; 26 27 import java.io.ByteArrayInputStream; 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.StringWriter; 32 import java.nio.ByteBuffer; 33 import java.nio.ByteOrder; 34 import java.nio.charset.Charset; 35 import java.nio.charset.StandardCharsets; 36 import java.nio.charset.UnsupportedCharsetException; 37 import java.nio.file.Files; 38 import java.nio.file.Paths; 39 import java.security.Permission; 40 import java.util.ArrayList; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.concurrent.ConcurrentHashMap; 45 import java.util.function.Supplier; 46 import java.util.regex.Pattern; 47 import java.util.stream.Collectors; 48 import java.util.stream.Stream; 49 50 import javax.xml.parsers.DocumentBuilder; 51 import javax.xml.parsers.DocumentBuilderFactory; 52 import javax.xml.parsers.ParserConfigurationException; 53 import javax.xml.transform.Transformer; 54 import javax.xml.transform.TransformerException; 55 import javax.xml.transform.TransformerFactory; 56 import javax.xml.transform.dom.DOMSource; 57 import javax.xml.transform.stream.StreamResult; 58 59 import org.w3c.dom.Document; 60 import org.w3c.dom.Node; 61 import org.xml.sax.SAXException; 62 63 /** 64 * This is an interface provide basic support for JAXP functional test. 65 */ 66 public class JAXPTestUtilities { 67 /** 68 * Prefix for error message. 69 */ 70 public static final String ERROR_MSG_HEADER = "Unexcepted exception thrown:"; 71 72 /** 73 * Prefix for error message on clean up block. 74 */ 75 public static final String ERROR_MSG_CLEANUP = "Clean up failed on %s"; 76 77 /** 78 * Force using slash as File separator as we always use cygwin to test in 79 * Windows platform. 80 */ 81 public static final String FILE_SEP = "/"; 82 83 /** 84 * Current test directory. 85 */ 86 public static final String USER_DIR = 87 System.getProperty("user.dir", ".") + FILE_SEP;; 88 89 /** 90 * A map storing every test's current test file pointer. File number should 91 * be incremental and it's a thread-safe reading on this file number. 92 */ 93 private static final ConcurrentHashMap<Class, Integer> currentFileNumber 94 = new ConcurrentHashMap<>(); 95 96 /** 97 * BOM table for storing BOM header. 98 */ 99 private final static Map<String, byte[]> bom = new HashMap(); 100 101 /** 102 * Initialize all BOM headers. 103 */ 104 static { 105 bom.put("UTF-8", new byte[]{(byte)0xEF, (byte) 0xBB, (byte) 0xBF}); 106 bom.put("UTF-16BE", new byte[]{(byte)0xFE, (byte)0xFF}); 107 bom.put("UTF-16LE", new byte[]{(byte)0xFF, (byte)0xFE}); 108 bom.put("UTF-32BE", new byte[]{(byte)0x00, (byte)0x00, (byte)0xFE, (byte)0xFF}); 109 bom.put("UTF-32LE", new byte[]{(byte)0xFF, (byte)0xFE, (byte)0x00, (byte)0x00}); 110 } 111 112 /** 113 * Compare contents of golden file with test output file line by line. 114 * return true if they're identical. 115 * @param goldfile Golden output file name 116 * @param outputfile Test output file name 117 * @return true if two files are identical. 118 * false if two files are not identical. 119 * @throws IOException if an I/O error occurs reading from the file or a 120 * malformed or unmappable byte sequence is read. 121 */ 122 public static boolean compareWithGold(String goldfile, String outputfile) 123 throws IOException { 124 return compareWithGold(goldfile, outputfile, StandardCharsets.UTF_8); 125 } 126 127 /** 128 * Compare contents of golden file with test output file line by line. 129 * return true if they're identical. 130 * @param goldfile Golden output file name. 131 * @param outputfile Test output file name. 132 * @param cs the charset to use for decoding. 133 * @return true if two files are identical. 134 * false if two files are not identical. 135 * @throws IOException if an I/O error occurs reading from the file or a 136 * malformed or unmappable byte sequence is read. 137 */ 138 public static boolean compareWithGold(String goldfile, String outputfile, 139 Charset cs) throws IOException { 140 boolean isSame = Files.readAllLines(Paths.get(goldfile)). 141 equals(Files.readAllLines(Paths.get(outputfile), cs)); 142 if (!isSame) { 143 System.err.println("Golden file " + goldfile + " :"); 144 Files.readAllLines(Paths.get(goldfile)).forEach(System.err::println); 145 System.err.println("Output file " + outputfile + " :"); 146 Files.readAllLines(Paths.get(outputfile), cs).forEach(System.err::println); 147 } 148 return isSame; 149 } 150 151 /** 152 * Compare contents of golden file with test output list line by line. 153 * return true if they're identical. 154 * @param goldfile Golden output file name. 155 * @param lines test output list. 156 * @return true if file's content is identical to given list. 157 * false if file's content is not identical to given list. 158 * @throws IOException if an I/O error occurs reading from the file or a 159 * malformed or unmappable byte sequence is read 160 */ 161 public static boolean compareLinesWithGold(String goldfile, List<String> lines) 162 throws IOException { 163 return Files.readAllLines(Paths.get(goldfile)).equals(lines); 164 } 165 166 /** 167 * Compare contents of golden file with a test output string. 168 * return true if they're identical. 169 * @param goldfile Golden output file name. 170 * @param string test string. 171 * @return true if file's content is identical to given string. 172 * false if file's content is not identical to given string. 173 * @throws IOException if an I/O error occurs reading from the file or a 174 * malformed or unmappable byte sequence is read 175 */ 176 public static boolean compareStringWithGold(String goldfile, String string) 177 throws IOException { 178 return Files.readAllLines(Paths.get(goldfile)).stream().collect( 179 Collectors.joining(System.getProperty("line.separator"))) 180 .equals(string); 181 } 182 183 /** 184 * Compare contents of golden file with test output file by their document 185 * representation. 186 * Here we ignore the white space and comments. return true if they're 187 * lexical identical. 188 * @param goldfile Golden output file name. 189 * @param resultFile Test output file name. 190 * @return true if two file's document representation are identical. 191 * false if two file's document representation are not identical. 192 * @throws javax.xml.parsers.ParserConfigurationException if the 193 * implementation is not available or cannot be instantiated. 194 * @throws SAXException If any parse errors occur. 195 * @throws IOException if an I/O error occurs reading from the file or a 196 * malformed or unmappable byte sequence is read . 197 */ 198 public static boolean compareDocumentWithGold(String goldfile, String resultFile) 199 throws ParserConfigurationException, SAXException, IOException { 200 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 201 factory.setNamespaceAware(true); 202 factory.setCoalescing(true); 203 factory.setIgnoringElementContentWhitespace(true); 204 factory.setIgnoringComments(true); 205 DocumentBuilder db = factory.newDocumentBuilder(); 206 207 Document goldD = db.parse(Paths.get(goldfile).toFile()); 208 goldD.normalizeDocument(); 209 Document resultD = db.parse(Paths.get(resultFile).toFile()); 210 resultD.normalizeDocument(); 211 return goldD.isEqualNode(resultD); 212 } 213 214 /** 215 * Compare contents of golden file with the serialization represent by given 216 * DOM node. 217 * Here we ignore the white space and comments. return true if they're 218 * lexical identical. 219 * @param goldfile Golden output file name. 220 * @param node A DOM node instance. 221 * @return true if file's content is identical to given node's serialization 222 * represent. 223 * false if file's content is not identical to given node's 224 * serialization represent. 225 * @throws TransformerException If an unrecoverable error occurs during the 226 * course of the transformation.. 227 * @throws IOException if an I/O error occurs reading from the file or a 228 * malformed or unmappable byte sequence is read . 229 */ 230 public static boolean compareSerializeDOMWithGold(String goldfile, Node node) 231 throws TransformerException, IOException { 232 TransformerFactory factory = TransformerFactory.newInstance(); 233 // Use identity transformer to serialize 234 Transformer identityTransformer = factory.newTransformer(); 235 StringWriter sw = new StringWriter(); 236 StreamResult streamResult = new StreamResult(sw); 237 DOMSource nodeSource = new DOMSource(node); 238 identityTransformer.transform(nodeSource, streamResult); 239 return compareStringWithGold(goldfile, sw.toString()); 240 } 241 242 /** 243 * Convert stream to ByteArrayInputStream by given character set. 244 * @param charset target character set. 245 * @param file a file that contains no BOM head content. 246 * @return a ByteArrayInputStream contains BOM heads and bytes in original 247 * stream 248 * @throws IOException I/O operation failed or unsupported character set. 249 */ 250 public static InputStream bomStream(String charset, String file) 251 throws IOException { 252 String localCharset = charset; 253 if (charset.equals("UTF-16") || charset.equals("UTF-32")) { 254 localCharset 255 += ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN ? "BE" : "LE"; 256 } 257 if (!bom.containsKey(localCharset)) 258 throw new UnsupportedCharsetException("Charset:" + localCharset); 259 260 byte[] content = Files.readAllLines(Paths.get(file)).stream(). 261 collect(Collectors.joining()).getBytes(localCharset); 262 byte[] head = bom.get(localCharset); 263 ByteBuffer bb = ByteBuffer.allocate(content.length + head.length); 264 bb.put(head); 265 bb.put(content); 266 return new ByteArrayInputStream(bb.array()); 267 } 268 269 /** 270 * Worker method to detect common absolute URLs. 271 * 272 * @param s String path\filename or URL (or any, really) 273 * @return true if s starts with a common URI scheme (namely 274 * the ones found in the examples of RFC2396); false otherwise 275 */ 276 protected static boolean isCommonURL(String s) { 277 if (null == s) 278 return false; 279 return Pattern.compile("^(file:|http:|ftp:|gopher:|mailto:|news:|telnet:)") 280 .matcher(s).matches(); 281 } 282 283 /** 284 * Utility method to translate a String filename to URL. 285 * 286 * If the name starts with a common URI scheme (namely the ones 287 * found in the examples of RFC2396), then simply return the 288 * name as-is (the assumption is that it's already a URL). 289 * Otherwise we attempt (cheaply) to convert to a file:/ URL. 290 * 291 * @param filename local path/filename of a file. 292 * @return a file:/ URL if filename represent a file, the same string if 293 * it appears to already be a URL. 294 */ 295 public static String filenameToURL(String filename) { 296 return Paths.get(filename).toUri().toASCIIString(); 297 } 298 299 /** 300 * Prints error message if an exception is thrown 301 * @param ex The exception is thrown by test. 302 */ 303 public static void failUnexpected(Throwable ex) { 304 fail(ERROR_MSG_HEADER, ex); 305 } 306 307 /** 308 * Prints error message if an exception is thrown when clean up a file. 309 * @param ex The exception is thrown in cleaning up a file. 310 * @param name Cleaning up file name. 311 */ 312 public static void failCleanup(IOException ex, String name) { 313 fail(String.format(ERROR_MSG_CLEANUP, name), ex); 314 } 315 316 /** 317 * Retrieve next test output file name. This method is a thread-safe method. 318 * @param clazz test class. 319 * @return next test output file name. 320 */ 321 public static String getNextFile(Class clazz) { 322 int nextNumber = currentFileNumber.contains(clazz) 323 ? currentFileNumber.get(clazz) + 1 : 1; 324 Integer i = currentFileNumber.putIfAbsent(clazz, nextNumber); 325 if (i != null) { 326 do { 327 nextNumber = currentFileNumber.get(clazz) + 1; 328 } while (!currentFileNumber.replace(clazz, nextNumber - 1, nextNumber)); 329 } 330 return USER_DIR + clazz.getName() + nextNumber + ".out"; 331 } 332 333 /** 334 * Acquire a full path string by given class name and relative path string. 335 * @param clazz Class name for the test. 336 * @param relativeDir relative path between java source file and expected 337 * path. 338 * @return a string represents the full path of accessing path. 339 */ 340 public static String getPathByClassName(Class clazz, String relativeDir) { 341 String packageName = FILE_SEP + 342 clazz.getPackage().getName().replaceAll("[.]", FILE_SEP); 343 String javaSourcePath = System.getProperty("test.src").replaceAll("\\" + File.separator, FILE_SEP) 344 + packageName + FILE_SEP; 345 String normalizedPath = Paths.get(javaSourcePath, relativeDir).normalize(). 346 toAbsolutePath().toString(); 347 return normalizedPath.replace("\\", FILE_SEP) + FILE_SEP; 348 } 349 350 351 /** 352 * Run the RunnableWithException with creating a JAXPPolicyManager and 353 * assigning temporary permissions. It's not thread-safe to use this 354 * function. 355 * 356 * @param r 357 * RunnableWithException to execute 358 * @param ps 359 * assigning permissions to add. 360 */ 361 public static void tryRunWithPolicyManager(RunnableWithException r, Permission... ps) throws Exception { 362 JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(true); 363 if (policyManager != null) 364 Stream.of(ps).forEach(p -> policyManager.addTmpPermission(p)); 365 try { 366 r.run(); 367 } finally { 368 JAXPPolicyManager.teardownPolicyManager(); 369 } 370 } 371 372 /** 373 * Run the runnable with assigning temporary permissions. This won't impact 374 * global policy. 375 * 376 * @param r 377 * Runnable to run 378 * @param ps 379 * assigning permissions to add. 380 */ 381 public static void runWithTmpPermission(Runnable r, Permission... ps) { 382 JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(false); 383 List<Integer> tmpPermissionIndexes = new ArrayList(); 384 if (policyManager != null) 385 Stream.of(ps).forEach(p -> tmpPermissionIndexes.add(policyManager.addTmpPermission(p))); 386 try { 387 r.run(); 388 } finally { 389 tmpPermissionIndexes.forEach(index -> policyManager.removeTmpPermission(index)); 390 } 391 } 392 393 /** 394 * Run the supplier with assigning temporary permissions. This won't impact 395 * global policy. 396 * 397 * @param s 398 * Supplier to run 399 * @param ps 400 * assigning permissions to add. 401 */ 402 public static <T> T runWithTmpPermission(Supplier<T> s, Permission... ps) { 403 JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(false); 404 List<Integer> tmpPermissionIndexes = new ArrayList(); 405 if (policyManager != null) 406 Stream.of(ps).forEach(p -> tmpPermissionIndexes.add(policyManager.addTmpPermission(p))); 407 try { 408 return s.get(); 409 } finally { 410 tmpPermissionIndexes.forEach(index -> policyManager.removeTmpPermission(index)); 411 } 412 } 413 414 /** 415 * Run the RunnableWithException with assigning temporary permissions. This won't impact 416 * global policy. 417 * 418 * @param r 419 * RunnableWithException to execute 420 * @param ps 421 * assigning permissions to add. 422 */ 423 public static void tryRunWithTmpPermission(RunnableWithException r, Permission... ps) throws Exception { 424 JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(false); 425 List<Integer> tmpPermissionIndexes = new ArrayList(); 426 if (policyManager != null) 427 Stream.of(ps).forEach(p -> tmpPermissionIndexes.add(policyManager.addTmpPermission(p))); 428 try { 429 r.run(); 430 } finally { 431 tmpPermissionIndexes.forEach(index -> policyManager.removeTmpPermission(index)); 432 } 433 } 434 435 @FunctionalInterface 436 public interface RunnableWithException { 437 void run() throws Exception; 438 } 439 }