1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.agent;
  28 
  29 import java.io.Reader;
  30 import java.io.BufferedReader;
  31 import java.io.InputStreamReader;
  32 import java.io.IOException;
  33 import java.lang.reflect.Constructor;
  34 import java.lang.reflect.InvocationTargetException;
  35 import java.net.URL;
  36 import java.nio.charset.StandardCharsets;
  37 import java.util.Enumeration;
  38 import java.util.Vector;
  39 import com.sun.javatest.util.StringArray;
  40 import java.io.PrintStream;
  41 
  42 /**
  43  * A map provides a simple translation table for between configuration values
  44  * as provided by JT Harness and as required by an Agent. Typically, this is for
  45  * adjusting filenames because of differences introduced by different mount points
  46  * for various file systems.
  47  * <p>
  48  * This facility has been largely superceded by the map substitution mechanism
  49  * provided by environment files.
  50  */
  51 public class Map {
  52     /**
  53      * Read a map from a specified file.  This code is deliberately
  54      * written to tolerate Java platforms without a file system ...
  55      * as in some versions of Personal Java. Any problems caused by
  56      * the file system not being present are returned as IOExceptions
  57      * with a suitable detail message.
  58      * @param name The name of the file to read
  59      * @return The map read from the given file
  60      * @throws IOException if any errors occurred reading the file
  61      */
  62     public static Map readFile(String name) throws IOException {
  63         try {
  64             Class<?> c = Class.forName("java.io.FileReader"); // optional API in Jersonal Java
  65             Constructor m = c.getConstructor(new Class[] {String.class});
  66             Reader r = (Reader)(m.newInstance(new Object[] {name}));
  67             return new Map(r);
  68         }
  69         catch (ClassNotFoundException e) {
  70             throw new IOException("file system not accessible (" + e + ")");
  71         }
  72         catch (InvocationTargetException e) {
  73             Throwable t = e.getTargetException();
  74             if (t instanceof IOException)
  75                 throw (IOException)t;
  76             else
  77                 throw fileSystemProblem(t);
  78         }
  79         catch (IllegalAccessException e) {
  80             throw fileSystemProblem(e);
  81         }
  82         catch (InstantiationException e) {
  83             throw fileSystemProblem(e);
  84         }
  85         catch (NoSuchMethodException e) {
  86             throw fileSystemProblem(e);
  87         }
  88         catch (Error e) {
  89             throw fileSystemProblem(e);
  90         }
  91     }
  92 
  93     /**
  94      * Read a map from a URL.
  95      * @param u The URL to read
  96      * @return The map read from the given URL
  97      * @throws IOException if any errors occurred reading the URL
  98      */
  99     public static Map readURL(URL u) throws IOException {
 100         return new Map(new InputStreamReader(u.openStream(), StandardCharsets.UTF_8.name()));
 101     }
 102 
 103 
 104     /**
 105      * Read a map from a local file (if the name does not begin with http:)
 106      * or from a URL if it does.  The method is simply a wrapper that delegates
 107      * to either {@link #readFile} or {@link #readURL} depending on the
 108      * argument.
 109      * @param name The name of the file or URL to read
 110      * @return The map read from the given location
 111      * @throws IOException if any errors occurred reading the map
 112      */
 113     public static Map readFileOrURL(String name) throws IOException {
 114         if (name.length() > 5 && name.substring(0, 5).equalsIgnoreCase("http:"))
 115             return readURL(new URL(name));
 116         else
 117             return readFile(name);
 118     }
 119 
 120     private static IOException fileSystemProblem(Throwable t) {
 121         return new IOException("problem accessing file system: " + t);
 122     }
 123 
 124     /**
 125      * Create a map by reading it from a given stream.
 126      * The '\u0020' sequence could be used to specify space symbol in the values.
 127      * @param r         The reader from which to read the map data. The reader is closed
 128      *                  after it has been completely read
 129      * @throws IOException if problems occur while reading the map data.
 130      */
 131     public Map(Reader r) throws IOException {
 132         BufferedReader in =
 133             (r instanceof BufferedReader ? (BufferedReader)r : new BufferedReader(r))
 134 ;
 135         // data arrives in rows, but we want it in columns
 136         Vector<String> from = new Vector<>();
 137         Vector<String> to = new Vector<>();
 138         String line;
 139         while ((line = in.readLine()) != null) {
 140             line = line.trim();
 141             if (line.length() > 0  &&  !line.startsWith("#")) {
 142                 String[] row = StringArray.split(line);
 143                 if (row.length < 2)
 144                     throw new IOException("format error in map file, line is: " + line);
 145                 from.addElement(row[0].replaceAll("\\Q\\u0020\\E", " "));
 146                 to.addElement(row[1].replaceAll("\\Q\\u0020\\E", " "));
 147             }
 148         }
 149         in.close();
 150 
 151         fromValues = new String[from.size()];
 152         from.copyInto(fromValues);
 153         toValues = new String[to.size()];
 154         to.copyInto(toValues);
 155     }
 156 
 157     /**
 158      * Translate the strings according to values in the map.
 159      * The strings are updated in place.
 160      * @param args      An array of strings to be translated according to the data
 161      *                  the map.
 162      */
 163     public void map(String[] args) {
 164         if (fromValues == null)
 165             return; // empty table
 166 
 167         for (int i = 0; i < args.length; i++) {
 168             String arg = args[i];
 169             for (int j = 0; j < fromValues.length; j++) {
 170                 String f = fromValues[j];
 171                 String t = toValues[j];
 172                 for (int index = arg.indexOf(f);
 173                      index != -1;
 174                      index = arg.indexOf(f, index + t.length())) {
 175                     arg = arg.substring(0, index) + t + arg.substring(index + f.length());
 176                 }
 177             }
 178             if (tracing && arg != args[i]) {
 179                 traceOut.println("MAPARG-from: " + args[i]);
 180                 traceOut.println("MAPARG-to:   " + arg);
 181             }
 182             args[i] = arg;
 183         }
 184     }
 185 
 186     /**
 187      * Creates a map from the specified <code>java.util.Map</code>.
 188      *
 189      * @param map the java.util.Map instance to take key-value pairs from
 190      */
 191     public Map(java.util.Map<String, String> map) {
 192         fromValues = new String[map.size()];
 193         toValues = new String[map.size()];
 194         int index = 0;
 195         for (java.util.Map.Entry<String, String> entry : map.entrySet()) {
 196             fromValues[index] = entry.getKey();
 197             toValues[index] = entry.getValue();
 198             index++;
 199         }
 200     }
 201 
 202     /**
 203      * Enumerate the entries of the map.
 204      * @return an enumeration of the translation entries within the map
 205      */
 206     public Enumeration<String[]> enumerate() {
 207         Vector<String[]> v = new Vector<>(fromValues.length);
 208         for (int i = 0; i < fromValues.length; i++) {
 209             v.addElement(new String[] {fromValues[i], toValues[i]});
 210         }
 211         return v.elements();
 212     }
 213 
 214     public void setTracing(boolean state, PrintStream out) {
 215         tracing = state;
 216         if (state) {
 217             traceOut = out;
 218         }
 219         else {
 220             traceOut = null;
 221         }
 222     }
 223 
 224     private boolean tracing;
 225     private PrintStream traceOut;
 226     private String[] fromValues;
 227     private String[] toValues;
 228 }