1 /*
   2  * Copyright (c) 2014, 2017, 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.Optional;
  45 import java.util.concurrent.Callable;
  46 import java.util.concurrent.ConcurrentHashMap;
  47 import java.util.function.Supplier;
  48 import java.util.regex.Pattern;
  49 import java.util.stream.Collectors;
  50 
  51 import javax.xml.parsers.DocumentBuilder;
  52 import javax.xml.parsers.DocumentBuilderFactory;
  53 import javax.xml.parsers.ParserConfigurationException;
  54 import javax.xml.transform.Transformer;
  55 import javax.xml.transform.TransformerException;
  56 import javax.xml.transform.TransformerFactory;
  57 import javax.xml.transform.dom.DOMSource;
  58 import javax.xml.transform.stream.StreamResult;
  59 
  60 import org.w3c.dom.Document;
  61 import org.w3c.dom.Node;
  62 import org.xml.sax.SAXException;
  63 
  64 /**
  65  * This is an interface provide basic support for JAXP functional test.
  66  */
  67 public class JAXPTestUtilities {
  68     /**
  69      * Prefix for error message.
  70      */
  71     public static final String ERROR_MSG_HEADER = "Unexcepted exception thrown:";
  72 
  73     /**
  74      * Prefix for error message on clean up block.
  75      */
  76     public static final String ERROR_MSG_CLEANUP = "Clean up failed on %s";
  77 
  78     /**
  79      * Force using slash as File separator as we always use cygwin to test in
  80      * Windows platform.
  81      */
  82     public static final String FILE_SEP = "/";
  83 
  84     /**
  85      * A map storing every test's current test file pointer. File number should
  86      * be incremental and it's a thread-safe reading on this file number.
  87      */
  88     private static final ConcurrentHashMap<Class<?>, Integer> currentFileNumber
  89                 = new ConcurrentHashMap<>();
  90 
  91     /**
  92      * BOM table for storing BOM header.
  93      */
  94     private final static Map<String, byte[]> bom = new HashMap<>();
  95 
  96     /**
  97      * Initialize all BOM headers.
  98      */
  99     static {
 100         bom.put("UTF-8", new byte[]{(byte)0xEF, (byte) 0xBB, (byte) 0xBF});
 101         bom.put("UTF-16BE", new byte[]{(byte)0xFE, (byte)0xFF});
 102         bom.put("UTF-16LE", new byte[]{(byte)0xFF, (byte)0xFE});
 103         bom.put("UTF-32BE", new byte[]{(byte)0x00, (byte)0x00, (byte)0xFE, (byte)0xFF});
 104         bom.put("UTF-32LE", new byte[]{(byte)0xFF, (byte)0xFE, (byte)0x00, (byte)0x00});
 105     }
 106 
 107     /**
 108      * Compare contents of golden file with test output file line by line.
 109      * return true if they're identical.
 110      * @param goldfile Golden output file name
 111      * @param outputfile Test output file name
 112      * @return true if two files are identical.
 113      *         false if two files are not identical.
 114      * @throws IOException if an I/O error occurs reading from the file or a
 115      *         malformed or unmappable byte sequence is read.
 116      */
 117     public static boolean compareWithGold(String goldfile, String outputfile)
 118             throws IOException {
 119         return compareWithGold(goldfile, outputfile, StandardCharsets.UTF_8);
 120     }
 121 
 122     /**
 123      * Compare contents of golden file with test output file line by line.
 124      * return true if they're identical.
 125      * @param goldfile Golden output file name.
 126      * @param outputfile Test output file name.
 127      * @param cs the charset to use for decoding.
 128      * @return true if two files are identical.
 129      *         false if two files are not identical.
 130      * @throws IOException if an I/O error occurs reading from the file or a
 131      *         malformed or unmappable byte sequence is read.
 132      */
 133     public static boolean compareWithGold(String goldfile, String outputfile,
 134              Charset cs) throws IOException {
 135         boolean isSame = Files.readAllLines(Paths.get(goldfile)).
 136                 equals(Files.readAllLines(Paths.get(outputfile), cs));
 137         if (!isSame) {
 138             System.err.println("Golden file " + goldfile + " :");
 139             Files.readAllLines(Paths.get(goldfile)).forEach(System.err::println);
 140             System.err.println("Output file " + outputfile + " :");
 141             Files.readAllLines(Paths.get(outputfile), cs).forEach(System.err::println);
 142         }
 143         return isSame;
 144     }
 145 
 146     /**
 147      * Compare contents of golden file with test output list line by line.
 148      * return true if they're identical.
 149      * @param goldfile Golden output file name.
 150      * @param lines test output list.
 151      * @return true if file's content is identical to given list.
 152      *         false if file's content is not identical to given list.
 153      * @throws IOException if an I/O error occurs reading from the file or a
 154      *         malformed or unmappable byte sequence is read
 155      */
 156     public static boolean compareLinesWithGold(String goldfile, List<String> lines)
 157             throws IOException {
 158         return Files.readAllLines(Paths.get(goldfile)).equals(lines);
 159     }
 160 
 161     /**
 162      * Compare contents of golden file with a test output string.
 163      * return true if they're identical.
 164      * @param goldfile Golden output file name.
 165      * @param string test string.
 166      * @return true if file's content is identical to given string.
 167      *         false if file's content is not identical to given string.
 168      * @throws IOException if an I/O error occurs reading from the file or a
 169      *         malformed or unmappable byte sequence is read
 170      */
 171     public static boolean compareStringWithGold(String goldfile, String string)
 172             throws IOException {
 173         return Files.readAllLines(Paths.get(goldfile)).stream().collect(
 174                 Collectors.joining(System.getProperty("line.separator")))
 175                 .equals(string);
 176     }
 177 
 178     /**
 179      * Compare contents of golden file with test output file by their document
 180      * representation.
 181      * Here we ignore the white space and comments. return true if they're
 182      * lexical identical.
 183      * @param goldfile Golden output file name.
 184      * @param resultFile Test output file name.
 185      * @return true if two file's document representation are identical.
 186      *         false if two file's document representation are not identical.
 187      * @throws javax.xml.parsers.ParserConfigurationException if the
 188      *         implementation is not available or cannot be instantiated.
 189      * @throws SAXException If any parse errors occur.
 190      * @throws IOException if an I/O error occurs reading from the file or a
 191      *         malformed or unmappable byte sequence is read .
 192      */
 193     public static boolean compareDocumentWithGold(String goldfile, String resultFile)
 194             throws ParserConfigurationException, SAXException, IOException {
 195         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 196         factory.setNamespaceAware(true);
 197         factory.setCoalescing(true);
 198         factory.setIgnoringElementContentWhitespace(true);
 199         factory.setIgnoringComments(true);
 200         DocumentBuilder db = factory.newDocumentBuilder();
 201 
 202         Document goldD = db.parse(Paths.get(goldfile).toFile());
 203         goldD.normalizeDocument();
 204         Document resultD = db.parse(Paths.get(resultFile).toFile());
 205         resultD.normalizeDocument();
 206         return goldD.isEqualNode(resultD);
 207     }
 208 
 209     /**
 210      * Compare contents of golden file with the serialization represent by given
 211      * DOM node.
 212      * Here we ignore the white space and comments. return true if they're
 213      * lexical identical.
 214      * @param goldfile Golden output file name.
 215      * @param node A DOM node instance.
 216      * @return true if file's content is identical to given node's serialization
 217      *         represent.
 218      *         false if file's content is not identical to given node's
 219      *         serialization represent.
 220      * @throws TransformerException If an unrecoverable error occurs during the
 221      *         course of the transformation..
 222      * @throws IOException if an I/O error occurs reading from the file or a
 223      *         malformed or unmappable byte sequence is read .
 224      */
 225     public static boolean compareSerializeDOMWithGold(String goldfile, Node node)
 226             throws TransformerException, IOException {
 227         TransformerFactory factory = TransformerFactory.newInstance();
 228         // Use identity transformer to serialize
 229         Transformer identityTransformer = factory.newTransformer();
 230         StringWriter sw = new StringWriter();
 231         StreamResult streamResult = new StreamResult(sw);
 232         DOMSource nodeSource = new DOMSource(node);
 233         identityTransformer.transform(nodeSource, streamResult);
 234         return compareStringWithGold(goldfile, sw.toString());
 235     }
 236 
 237     /**
 238      * Convert stream to ByteArrayInputStream by given character set.
 239      * @param charset target character set.
 240      * @param file a file that contains no BOM head content.
 241      * @return a ByteArrayInputStream contains BOM heads and bytes in original
 242      *         stream
 243      * @throws IOException I/O operation failed or unsupported character set.
 244      */
 245     public static InputStream bomStream(String charset, String file)
 246             throws IOException {
 247         String localCharset = charset;
 248         if (charset.equals("UTF-16") || charset.equals("UTF-32")) {
 249             localCharset
 250                 += ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN ? "BE" : "LE";
 251         }
 252         if (!bom.containsKey(localCharset))
 253             throw new UnsupportedCharsetException("Charset:" + localCharset);
 254 
 255         byte[] content = Files.readAllLines(Paths.get(file)).stream().
 256                 collect(Collectors.joining()).getBytes(localCharset);
 257         byte[] head = bom.get(localCharset);
 258         ByteBuffer bb = ByteBuffer.allocate(content.length + head.length);
 259         bb.put(head);
 260         bb.put(content);
 261         return new ByteArrayInputStream(bb.array());
 262     }
 263 
 264    /**
 265      * Worker method to detect common absolute URLs.
 266      *
 267      * @param s String path\filename or URL (or any, really)
 268      * @return true if s starts with a common URI scheme (namely
 269      * the ones found in the examples of RFC2396); false otherwise
 270      */
 271     protected static boolean isCommonURL(String s) {
 272         if (null == s)
 273             return false;
 274         return Pattern.compile("^(file:|http:|ftp:|gopher:|mailto:|news:|telnet:)")
 275                 .matcher(s).matches();
 276     }
 277 
 278     /**
 279      * Utility method to translate a String filename to URL.
 280      *
 281      * If the name starts with a common URI scheme (namely the ones
 282      * found in the examples of RFC2396), then simply return the
 283      * name as-is (the assumption is that it's already a URL).
 284      * Otherwise we attempt (cheaply) to convert to a file:/ URL.
 285      *
 286      * @param filename local path/filename of a file.
 287      * @return a file:/ URL if filename represent a file, the same string if
 288      *         it appears to already be a URL.
 289      */
 290     public static String filenameToURL(String filename) {
 291         return Paths.get(filename).toUri().toASCIIString();
 292     }
 293 
 294     /**
 295      * Prints error message if an exception is thrown
 296      * @param ex The exception is thrown by test.
 297      */
 298     public static void failUnexpected(Throwable ex) {
 299         fail(ERROR_MSG_HEADER, ex);
 300     }
 301 
 302     /**
 303      * Prints error message if an exception is thrown when clean up a file.
 304      * @param ex The exception is thrown in cleaning up a file.
 305      * @param name Cleaning up file name.
 306      */
 307     public static void failCleanup(IOException ex, String name) {
 308         fail(String.format(ERROR_MSG_CLEANUP, name), ex);
 309     }
 310 
 311     /**
 312      * Retrieve next test output file name. This method is a thread-safe method.
 313      * @param clazz test class.
 314      * @return next test output file name.
 315      */
 316     public static String getNextFile(Class<?> clazz) {
 317         int nextNumber = currentFileNumber.contains(clazz)
 318                 ? currentFileNumber.get(clazz) + 1 : 1;
 319         Integer i = currentFileNumber.putIfAbsent(clazz, nextNumber);
 320         if (i != null) {
 321             do {
 322                 nextNumber = currentFileNumber.get(clazz) + 1;
 323             } while (!currentFileNumber.replace(clazz, nextNumber - 1, nextNumber));
 324         }
 325         return USER_DIR + clazz.getName() + nextNumber + ".out";
 326     }
 327 
 328     /**
 329      * Acquire a full path string by given class name and relative path string.
 330      * @param clazz Class name for the test.
 331      * @param relativeDir relative path between java source file and expected
 332      *        path.
 333      * @return a string represents the full path of accessing path.
 334      */
 335     public static String getPathByClassName(Class<?> clazz, String relativeDir) {
 336         String javaSourcePath = System.getProperty("test.src").replaceAll("\\" + File.separator, FILE_SEP);
 337         String normalizedPath = Paths.get(javaSourcePath, relativeDir).normalize().
 338                 toAbsolutePath().toString();
 339         return normalizedPath.replace("\\", FILE_SEP) + FILE_SEP;
 340     }
 341 
 342 
 343     /**
 344      * Run the supplier with all permissions. This won't impact global policy.
 345      *
 346      * @param s
 347      *            Supplier to run
 348      */
 349     public static <T> T runWithAllPerm(Supplier<T> s) {
 350         Optional<JAXPPolicyManager> policyManager = Optional.ofNullable(JAXPPolicyManager
 351                 .getJAXPPolicyManager(false));
 352         policyManager.ifPresent(manager -> manager.setAllowAll(true));
 353         try {
 354             return s.get();
 355         } finally {
 356             policyManager.ifPresent(manager -> manager.setAllowAll(false));
 357         }
 358     }
 359 
 360     /**
 361      * Run the supplier with all permissions. This won't impact global policy.
 362      *
 363      * @param s
 364      *            Supplier to run
 365      */
 366     public static <T> T tryRunWithAllPerm(Callable<T> c) throws Exception {
 367         Optional<JAXPPolicyManager> policyManager = Optional.ofNullable(JAXPPolicyManager
 368                 .getJAXPPolicyManager(false));
 369         policyManager.ifPresent(manager -> manager.setAllowAll(true));
 370         try {
 371             return c.call();
 372         } finally {
 373             policyManager.ifPresent(manager -> manager.setAllowAll(false));
 374         }
 375     }
 376 
 377     /**
 378      * Run the Runnable with all permissions. This won't impact global policy.
 379      *
 380      * @param s
 381      *            Supplier to run
 382      */
 383     public static void runWithAllPerm(Runnable r) {
 384         Optional<JAXPPolicyManager> policyManager = Optional.ofNullable(JAXPPolicyManager
 385                 .getJAXPPolicyManager(false));
 386         policyManager.ifPresent(manager -> manager.setAllowAll(true));
 387         try {
 388             r.run();
 389         } finally {
 390             policyManager.ifPresent(manager -> manager.setAllowAll(false));
 391         }
 392     }
 393 
 394     /**
 395      * Acquire a system property.
 396      *
 397      * @param name
 398      *            System property name to be acquired.
 399      * @return property value
 400      */
 401     public static String getSystemProperty(String name) {
 402         return runWithAllPerm(() -> System.getProperty(name));
 403     }
 404 
 405     /**
 406      * Set a system property by given system value.
 407      *
 408      * @param name
 409      *            System property name to be set.
 410      * @param value
 411      *            System property value to be set.
 412      */
 413     public static void setSystemProperty(String name, String value) {
 414         runWithAllPerm(() -> System.setProperty(name, value));
 415     }
 416 
 417     /**
 418      * Clear a system property.
 419      *
 420      * @param name
 421      *            System property name to be cleared.
 422      */
 423     public static void clearSystemProperty(String name) {
 424         runWithAllPerm(() -> System.clearProperty(name));
 425     }
 426 
 427     /**
 428      * Run the runnable with assigning temporary permissions. This won't impact
 429      * global policy.
 430      *
 431      * @param r
 432      *            Runnable to run
 433      * @param ps
 434      *            assigning permissions to add.
 435      */
 436     public static void runWithTmpPermission(Runnable r, Permission... ps) {
 437         JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(false);
 438         List<Integer> tmpPermissionIndexes = new ArrayList<>();
 439         if (policyManager != null) {
 440             for (Permission p : ps)
 441                 tmpPermissionIndexes.add(policyManager.addTmpPermission(p));
 442         }
 443         try {
 444             r.run();
 445         } finally {
 446             for (int index: tmpPermissionIndexes)
 447                 policyManager.removeTmpPermission(index);
 448         }
 449     }
 450 
 451     /**
 452      * Run the supplier with assigning temporary permissions. This won't impact
 453      * global policy.
 454      *
 455      * @param s
 456      *            Supplier to run
 457      * @param ps
 458      *            assigning permissions to add.
 459      */
 460     public static <T> T runWithTmpPermission(Supplier<T> s, Permission... ps) {
 461         JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(false);
 462         List<Integer> tmpPermissionIndexes = new ArrayList<>();
 463         if (policyManager != null) {
 464             for (Permission p : ps)
 465                 tmpPermissionIndexes.add(policyManager.addTmpPermission(p));
 466         }
 467         try {
 468             return s.get();
 469         } finally {
 470             for (int index: tmpPermissionIndexes)
 471                 policyManager.removeTmpPermission(index);
 472         }
 473     }
 474 
 475     /**
 476      * Run the RunnableWithException with assigning temporary permissions. This
 477      * won't impact global policy.
 478      *
 479      * @param r
 480      *            RunnableWithException to execute
 481      * @param ps
 482      *            assigning permissions to add.
 483      */
 484     public static void tryRunWithTmpPermission(RunnableWithException r, Permission... ps) throws Exception {
 485         JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(false);
 486         List<Integer> tmpPermissionIndexes = new ArrayList<>();
 487         if (policyManager != null) {
 488             for (Permission p : ps)
 489                 tmpPermissionIndexes.add(policyManager.addTmpPermission(p));
 490         }
 491         try {
 492             r.run();
 493         } finally {
 494             for (int index: tmpPermissionIndexes)
 495                 policyManager.removeTmpPermission(index);
 496         }
 497     }
 498 
 499     @FunctionalInterface
 500     public interface RunnableWithException {
 501         void run() throws Exception;
 502     }
 503 
 504     /**
 505      * Current test directory.
 506      */
 507     public static final String USER_DIR = getSystemProperty("user.dir") + FILE_SEP;;
 508 
 509 }