1 /*
   2  * Copyright (c) 2014, 2015, 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 java.io.ByteArrayInputStream;
  26 import java.io.File;
  27 import java.io.IOException;
  28 import java.io.InputStream;
  29 import java.io.StringWriter;
  30 import java.nio.ByteBuffer;
  31 import java.nio.ByteOrder;
  32 import java.nio.charset.Charset;
  33 import java.nio.charset.StandardCharsets;
  34 import java.nio.charset.UnsupportedCharsetException;
  35 import java.nio.file.Files;
  36 import java.nio.file.Paths;
  37 import java.util.HashMap;
  38 import java.util.List;
  39 import java.util.Map;
  40 import java.util.concurrent.ConcurrentHashMap;
  41 import java.util.regex.Pattern;
  42 import java.util.stream.Collectors;
  43 import javax.xml.parsers.DocumentBuilder;
  44 import javax.xml.parsers.DocumentBuilderFactory;
  45 import javax.xml.parsers.ParserConfigurationException;
  46 import javax.xml.transform.Transformer;
  47 import javax.xml.transform.TransformerException;
  48 import javax.xml.transform.TransformerFactory;
  49 import javax.xml.transform.dom.DOMSource;
  50 import javax.xml.transform.stream.StreamResult;
  51 import static org.testng.Assert.fail;
  52 import org.w3c.dom.Document;
  53 import org.w3c.dom.Node;
  54 import org.xml.sax.SAXException;
  55 
  56 /**
  57  * This is an interface provide basic support for JAXP functional test.
  58  */
  59 public class JAXPTestUtilities {
  60     /**
  61      * Prefix for error message.
  62      */
  63     public static final String ERROR_MSG_HEADER = "Unexcepted exception thrown:";
  64 
  65     /**
  66      * Prefix for error message on clean up block.
  67      */
  68     public static final String ERROR_MSG_CLEANUP = "Clean up failed on %s";
  69 
  70     /**
  71      * Force using slash as File separator as we always use cygwin to test in
  72      * Windows platform.
  73      */
  74     public static final String FILE_SEP = "/";
  75 
  76     /**
  77      * Current test directory.
  78      */
  79     public static final String USER_DIR =
  80             System.getProperty("user.dir", ".") + FILE_SEP;;
  81 
  82     /**
  83      * A map storing every test's current test file pointer. File number should
  84      * be incremental and it's a thread-safe reading on this file number.
  85      */
  86     private static final ConcurrentHashMap<Class, Integer> currentFileNumber
  87                 = new ConcurrentHashMap<>();
  88 
  89     /**
  90      * BOM table for storing BOM header.
  91      */
  92     private final static Map<String, byte[]> bom = new HashMap<>();
  93 
  94     /**
  95      * Initialize all BOM headers.
  96      */
  97     static {
  98         bom.put("UTF-8", new byte[]{(byte)0xEF, (byte) 0xBB, (byte) 0xBF});
  99         bom.put("UTF-16BE", new byte[]{(byte)0xFE, (byte)0xFF});
 100         bom.put("UTF-16LE", new byte[]{(byte)0xFF, (byte)0xFE});
 101         bom.put("UTF-32BE", new byte[]{(byte)0x00, (byte)0x00, (byte)0xFE, (byte)0xFF});
 102         bom.put("UTF-32LE", new byte[]{(byte)0xFF, (byte)0xFE, (byte)0x00, (byte)0x00});
 103     }
 104 
 105     /**
 106      * Compare contents of golden file with test output file line by line.
 107      * return true if they're identical.
 108      * @param goldfile Golden output file name
 109      * @param outputfile Test output file name
 110      * @return true if two files are identical.
 111      *         false if two files are not identical.
 112      * @throws IOException if an I/O error occurs reading from the file or a
 113      *         malformed or unmappable byte sequence is read.
 114      */
 115     public static boolean compareWithGold(String goldfile, String outputfile)
 116             throws IOException {
 117         return compareWithGold(goldfile, outputfile, StandardCharsets.UTF_8);
 118     }
 119 
 120     /**
 121      * Compare contents of golden file with test output file line by line.
 122      * return true if they're identical.
 123      * @param goldfile Golden output file name.
 124      * @param outputfile Test output file name.
 125      * @param cs the charset to use for decoding.
 126      * @return true if two files are identical.
 127      *         false if two files are not identical.
 128      * @throws IOException if an I/O error occurs reading from the file or a
 129      *         malformed or unmappable byte sequence is read.
 130      */
 131     public static boolean compareWithGold(String goldfile, String outputfile,
 132              Charset cs) throws IOException {
 133         return Files.readAllLines(Paths.get(goldfile)).
 134                 equals(Files.readAllLines(Paths.get(outputfile), cs));
 135     }
 136 
 137     /**
 138      * Compare contents of golden file with test output list line by line.
 139      * return true if they're identical.
 140      * @param goldfile Golden output file name.
 141      * @param lines test output list.
 142      * @return true if file's content is identical to given list.
 143      *         false if file's content is not identical to given list.
 144      * @throws IOException if an I/O error occurs reading from the file or a
 145      *         malformed or unmappable byte sequence is read
 146      */
 147     public static boolean compareLinesWithGold(String goldfile, List<String> lines)
 148             throws IOException {
 149         return Files.readAllLines(Paths.get(goldfile)).equals(lines);
 150     }
 151 
 152     /**
 153      * Compare contents of golden file with a test output string.
 154      * return true if they're identical.
 155      * @param goldfile Golden output file name.
 156      * @param string test string.
 157      * @return true if file's content is identical to given string.
 158      *         false if file's content is not identical to given string.
 159      * @throws IOException if an I/O error occurs reading from the file or a
 160      *         malformed or unmappable byte sequence is read
 161      */
 162     public static boolean compareStringWithGold(String goldfile, String string)
 163             throws IOException {
 164         return Files.readAllLines(Paths.get(goldfile)).stream().collect(
 165                 Collectors.joining(System.getProperty("line.separator")))
 166                 .equals(string);
 167     }
 168 
 169     /**
 170      * Compare contents of golden file with test output file by their document
 171      * representation.
 172      * Here we ignore the white space and comments. return true if they're
 173      * lexical identical.
 174      * @param goldfile Golden output file name.
 175      * @param resultFile Test output file name.
 176      * @return true if two file's document representation are identical.
 177      *         false if two file's document representation are not identical.
 178      * @throws javax.xml.parsers.ParserConfigurationException if the
 179      *         implementation is not available or cannot be instantiated.
 180      * @throws SAXException If any parse errors occur.
 181      * @throws IOException if an I/O error occurs reading from the file or a
 182      *         malformed or unmappable byte sequence is read .
 183      */
 184     public static boolean compareDocumentWithGold(String goldfile, String resultFile)
 185             throws ParserConfigurationException, SAXException, IOException {
 186         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 187         factory.setNamespaceAware(true);
 188         factory.setCoalescing(true);
 189         factory.setIgnoringElementContentWhitespace(true);
 190         factory.setIgnoringComments(true);
 191         DocumentBuilder db = factory.newDocumentBuilder();
 192 
 193         Document goldD = db.parse(Paths.get(goldfile).toFile());
 194         goldD.normalizeDocument();
 195         Document resultD = db.parse(Paths.get(resultFile).toFile());
 196         resultD.normalizeDocument();
 197         return goldD.isEqualNode(resultD);
 198     }
 199 
 200     /**
 201      * Compare contents of golden file with the serialization represent by given
 202      * DOM node.
 203      * Here we ignore the white space and comments. return true if they're
 204      * lexical identical.
 205      * @param goldfile Golden output file name.
 206      * @param node A DOM node instance.
 207      * @return true if file's content is identical to given node's serialization
 208      *         represent.
 209      *         false if file's content is not identical to given node's
 210      *         serialization represent.
 211      * @throws TransformerException If an unrecoverable error occurs during the
 212      *         course of the transformation..
 213      * @throws IOException if an I/O error occurs reading from the file or a
 214      *         malformed or unmappable byte sequence is read .
 215      */
 216     public static boolean compareSerializeDOMWithGold(String goldfile, Node node)
 217             throws TransformerException, IOException {
 218         TransformerFactory factory = TransformerFactory.newInstance();
 219         // Use identity transformer to serialize
 220         Transformer identityTransformer = factory.newTransformer();
 221         StringWriter sw = new StringWriter();
 222         StreamResult streamResult = new StreamResult(sw);
 223         DOMSource nodeSource = new DOMSource(node);
 224         identityTransformer.transform(nodeSource, streamResult);
 225         return compareStringWithGold(goldfile, sw.toString());
 226     }
 227 
 228     /**
 229      * Convert stream to ByteArrayInputStream by given character set.
 230      * @param charset target character set.
 231      * @param file a file that contains no BOM head content.
 232      * @return a ByteArrayInputStream contains BOM heads and bytes in original
 233      *         stream
 234      * @throws IOException I/O operation failed or unsupported character set.
 235      */
 236     public static InputStream bomStream(String charset, String file)
 237             throws IOException {
 238         String localCharset = charset;
 239         if (charset.equals("UTF-16") || charset.equals("UTF-32")) {
 240             localCharset
 241                 += ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN ? "BE" : "LE";
 242         }
 243         if (!bom.containsKey(localCharset))
 244             throw new UnsupportedCharsetException("Charset:" + localCharset);
 245 
 246         byte[] content = Files.readAllLines(Paths.get(file)).stream().
 247                 collect(Collectors.joining()).getBytes(localCharset);
 248         byte[] head = bom.get(localCharset);
 249         ByteBuffer bb = ByteBuffer.allocate(content.length + head.length);
 250         bb.put(head);
 251         bb.put(content);
 252         return new ByteArrayInputStream(bb.array());
 253     }
 254 
 255    /**
 256      * Worker method to detect common absolute URLs.
 257      *
 258      * @param s String path\filename or URL (or any, really)
 259      * @return true if s starts with a common URI scheme (namely
 260      * the ones found in the examples of RFC2396); false otherwise
 261      */
 262     protected static boolean isCommonURL(String s) {
 263         if (null == s)
 264             return false;
 265         return Pattern.compile("^(file:|http:|ftp:|gopher:|mailto:|news:|telnet:)")
 266                 .matcher(s).matches();
 267     }
 268 
 269     /**
 270      * Utility method to translate a String filename to URL.
 271      *
 272      * If the name starts with a common URI scheme (namely the ones
 273      * found in the examples of RFC2396), then simply return the
 274      * name as-is (the assumption is that it's already a URL).
 275      * Otherwise we attempt (cheaply) to convert to a file:/ URL.
 276      *
 277      * @param filename local path/filename of a file.
 278      * @return a file:/ URL if filename represent a file, the same string if
 279      *         it appears to already be a URL.
 280      */
 281     public static String filenameToURL(String filename) {
 282         return Paths.get(filename).toUri().toASCIIString();
 283     }
 284 
 285     /**
 286      * Prints error message if an exception is thrown
 287      * @param ex The exception is thrown by test.
 288      */
 289     public static void failUnexpected(Throwable ex) {
 290         fail(ERROR_MSG_HEADER, ex);
 291     }
 292 
 293     /**
 294      * Prints error message if an exception is thrown when clean up a file.
 295      * @param ex The exception is thrown in cleaning up a file.
 296      * @param name Cleaning up file name.
 297      */
 298     public static void failCleanup(IOException ex, String name) {
 299         fail(String.format(ERROR_MSG_CLEANUP, name), ex);
 300     }
 301 
 302     /**
 303      * Retrieve next test output file name. This method is a thread-safe method.
 304      * @param clazz test class.
 305      * @return next test output file name.
 306      */
 307     public static String getNextFile(Class clazz) {
 308         int nextNumber = currentFileNumber.contains(clazz)
 309                 ? currentFileNumber.get(clazz) + 1 : 1;
 310         Integer i = currentFileNumber.putIfAbsent(clazz, nextNumber);
 311         if (i != null && i != nextNumber) {
 312             do {
 313                 nextNumber = currentFileNumber.get(clazz) + 1;
 314             } while (currentFileNumber.replace(clazz, nextNumber -1, nextNumber));
 315         }
 316         return USER_DIR + clazz.getName() + nextNumber + ".out";
 317     }
 318 
 319     /**
 320      * Acquire a full path string by given class name and relative path string.
 321      * @param clazz Class name for the test.
 322      * @param relativeDir relative path between java source file and expected
 323      *        path.
 324      * @return a string represents the full path of accessing path.
 325      */
 326     public static String getPathByClassName(Class clazz, String relativeDir) {
 327         String packageName = FILE_SEP +
 328                 clazz.getPackage().getName().replaceAll("[.]", FILE_SEP);
 329         String javaSourcePath = System.getProperty("test.src").replaceAll("\\" + File.separator, FILE_SEP)
 330                 + packageName + FILE_SEP;
 331         String normalizedPath = Paths.get(javaSourcePath, relativeDir).normalize().
 332                 toAbsolutePath().toString();
 333         return normalizedPath.replace("\\", FILE_SEP) + FILE_SEP;
 334     }
 335 }