1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2009, 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.finder;
  28 
  29 import java.io.File;
  30 import java.util.HashMap;
  31 import java.util.Iterator;
  32 import java.util.Map;
  33 
  34 import com.sun.javatest.TestEnvironment;
  35 import com.sun.javatest.util.I18NResourceBundle;
  36 import com.sun.javatest.util.StringArray;
  37 
  38 /**
  39  * This class allows a new tag "@expand" which allows a single test
  40  * description to be expanded into multiple test descriptions using
  41  * variable substitution.  Variables are declared in the .jte file.
  42  *
  43  * The list of valid entries is identical to those found for JCK.
  44  * There is no list of pre-defined keywords.  If keyword checking
  45  * is needed, then the list of allowed keywords must be
  46  * provided via the <code>-allowKeyword</code> option.
  47  *
  48  * @see TagTestFinder
  49  */
  50 public class ExpandTestFinder extends TagTestFinder
  51 {
  52     //--------------------------------------------------------------------------
  53 
  54     // This code is only needed to ensure that this test finder
  55     // behaves the same as JCKTagTestFinder.  If that finder ever
  56     // derives from this one, it may be removed.
  57 
  58     public ExpandTestFinder() {
  59         validEntries = initTable(stdValidEntries);
  60         validEntries = addTableItem(validEntries, "test", TRUE);
  61         validKeywords = initTable(stdValidKeywords);
  62         validKeywords = initTable(new String[] { });
  63         addExtension(".jasm", JavaCommentStream.class);
  64         addExtension(".jcod", JavaCommentStream.class);
  65     } // ExpandTestFinder()
  66 
  67     protected int decodeArg(String[] args, int i) throws Fault {
  68         if (args[i].equals("-verify")) {
  69             verify = true;
  70             return 1;
  71         }
  72         else if (args[i].equals("-allowEntry")) {
  73             String e = args[i+1];
  74             validEntries.put(e.toLowerCase(), e);
  75             return 2;
  76         }
  77         else if (args[i].equals("-allowKeyword")) {
  78             String k = args[i+1];
  79             validKeywords.put(k.toLowerCase(), k);
  80             return 2;
  81         }
  82         else
  83             return super.decodeArg(args, i);
  84     } // decodeArg()
  85 
  86     private Map<String, String> initTable(String[] entries) {
  87         Map<String, String> map = new HashMap<>();
  88         for (int i = 0; i < entries.length; i++)
  89             map.put(entries[i].toLowerCase(), entries[i]);
  90         return map;
  91     } // initTable()
  92 
  93     private Map<String, String> addTableItem(Map<String, String> entries, String name, String value) {
  94         entries.put(name, value);
  95         return entries;
  96     } // addTableItem()
  97 
  98     private boolean verify;
  99     private Map<String, String> validEntries;
 100     private Map<String, String> validKeywords;
 101 
 102     private static final String TESTSUITE_HTML = "testsuite.html";
 103     // end of code for JCKTagTestFinder emulation
 104 
 105     //--------------------------------------------------------------------------
 106 
 107     public void init(String [] args, File testSuiteRoot, TestEnvironment env) throws Fault {
 108         // grab all environment variables open for expansion
 109         if (expandVars == null) {
 110             expandVars   = new HashMap<>(3);
 111             expandVarLen = new HashMap<>(3);
 112 
 113             for (Iterator i = env.keys().iterator(); i.hasNext(); ) {
 114                 try {
 115                     String n = (String) (i.next());
 116 
 117                     if (! n.startsWith("expand."))
 118                         continue;
 119                     String[] v =  env.lookup(n);
 120                     String fqvName = n.substring("expand.".length());
 121                     // add to hashtable of fully-qualified varNames
 122                     expandVars.put(fqvName, v);
 123 
 124                     int pos;
 125                     if ((pos = fqvName.indexOf(".")) == -1)
 126                         continue;
 127                     String stem = fqvName.substring(0, pos);
 128 
 129                     // checking lengths of co-joined variables
 130                     Integer len = expandVarLen.get(stem);
 131                     if (len == null) {
 132                         // add to hashtable of valid stems
 133                         expandVarLen.put(stem, new Integer(v.length));
 134                     } else {
 135                         if (v.length != len.intValue()) {
 136                             error(i18n, "expand.lengthMismatch",
 137                                   new Object[] {stem, fqvName.substring(pos+1)} );
 138                         }
 139                     }
 140                 } catch (TestEnvironment.Fault f) {
 141                     error(i18n, "expand.noDefn", f.getMessage());
 142                 }
 143             }
 144         }
 145 
 146         // This part of this method is only needed to ensure that this
 147         // test finder behaves the same as JCKTagTestFinder.  If that
 148         // finder ever derives from this one, it may be removed.
 149         if (testSuiteRoot.isDirectory()) {
 150             File f = new File(testSuiteRoot, TESTSUITE_HTML);
 151             if (!(f.exists() && !f.isDirectory() && f.canRead())) {
 152                 throw new Fault(i18n, "expand.badRootDir",
 153                                 new Object[] {TESTSUITE_HTML, testSuiteRoot.getPath()});
 154             }
 155         }
 156         else {
 157             String name = testSuiteRoot.getName();
 158             if (!name.equals(TESTSUITE_HTML)) {
 159                 throw new Fault(i18n, "expand.badRootFile");
 160             }
 161             // force the test suite root to be the directory
 162             testSuiteRoot = new File(testSuiteRoot.getParent());
 163         }
 164         // end of code for JCKTagTestFinder emulation
 165 
 166         super.init(args, testSuiteRoot, env);
 167     } // init()
 168 
 169     protected void foundTestDescription(Map<String, String> entries, File file, int line) {
 170         // cross-product and loop call up
 171         String origId = entries.get("id");
 172         if (origId == null)
 173             origId = "";
 174         Map<String, Integer> loopVars = new HashMap<>(3);
 175         foundTestDescription_1(entries, file, line, loopVars, origId);
 176     } // foundTestDescription()
 177 
 178     private void foundTestDescription_1(Map<String, String> entries, File file, int line, Map<String, Integer> loopVars, String id) {
 179         for (Iterator<String> iter = entries.keySet().iterator(); iter.hasNext(); ) {
 180             String name  = iter.next();
 181             String value = entries.get(name);
 182 //          System.out.println("------ NAME:  " + name + " VALUE: " + value);
 183 
 184             if (name.equals("title") || name.equals("test") || name.equals("id"))
 185                 // don't tokenize
 186                 continue;
 187 
 188             String [] words = StringArray.split(value);
 189             for (int i = 0; i < words.length; i++) {
 190                 if (words[i].startsWith("$")) {
 191                     String varName = words[i].substring(1);
 192 
 193                     // strip out {}'s
 194                     if (varName.startsWith("{")) {
 195                         if (! varName.endsWith("}")) {
 196                             error(i18n, "expand.missingCloseCurly", words[i]);
 197                             continue;
 198                         }
 199                         varName = varName.substring(1, varName.length()-1);
 200                     }
 201 
 202                     // separate foo.bar into stem=foo and qualifier=bar
 203                     String stem;
 204                     String qualifier;
 205                     int pos;
 206                     if ((pos = varName.indexOf(".")) == -1) {
 207                         stem = varName;
 208                         qualifier = "";
 209                     } else {
 210                         stem = varName.substring(0, pos);
 211                         qualifier = varName.substring(pos+1);
 212                     }
 213 
 214 //                  System.out.println("stem: " + stem + " qual: " + qualifier);
 215 
 216                     if (testStems.get(stem) == null)
 217                         // this is something we shouldn't expand
 218                         continue;
 219 
 220                     String [] valueList;
 221                     if ((valueList = expandVars.get(varName)) == null) {
 222                         //String [] msgs = {"unable to find `expand' definition in environment",
 223                         //words[i]};
 224                         //error(msgs);
 225                         continue;
 226                     }
 227 
 228                     String saveId = id;
 229                     Integer idx = loopVars.get(stem);
 230                     if (idx != null){
 231                         int j = idx.intValue();
 232                         words[i] = valueList[j];
 233 
 234                         id = saveId + "_" + words[i];
 235                         entries.put("id", id);
 236 
 237                         entries.put(name, StringArray.join(words));
 238                     } else {
 239                         for (int j = 0; j < valueList.length; j++) {
 240                             words[i] = valueList[j];
 241 
 242                             id = saveId + "_" + words[i];
 243                             entries.put("id", id);
 244 
 245                             entries.put(name, StringArray.join(words));
 246                             boolean loopy = !(qualifier.equals(""));
 247                             if (loopy) loopVars.put(stem, new Integer(j));
 248                             // clone needed here because we over-wrote words[i]
 249                             foundTestDescription_1(new HashMap<>(entries), file, line,
 250                                                    loopVars, id);
 251                             if (loopy) loopVars.remove(stem);
 252 
 253                         }
 254                         return;
 255                     }
 256                 }
 257             }
 258         }
 259         // clone may not be necessary, check for TestDescription upgrades
 260         // super.foundTestDescription(entries, file, line);
 261         super.foundTestDescription(new HashMap<>(entries), file, line);
 262     } // foundTestDescription_1()
 263 
 264     protected void processEntry(Map<String, String> entries, String name, String value) {
 265 //      System.out.println("NAME: " + name + " VALUE: " + value);
 266         if (name.equals("expand")) {
 267             if (testStems != null) {
 268                 error(i18n, "expand.multipleTags");
 269             }
 270             testStems = new HashMap<>(3);
 271             String [] stems = StringArray.split(value);
 272             for (int i = 0 ; i < stems.length; i++)
 273                 testStems.put(stems[i], TRUE);
 274         } else {
 275 
 276             // This part of this method is only needed to ensure that this
 277             // test finder behaves the same as JCKTagTestFinder.  If that
 278             // finder ever derives from this one, it may be removed.
 279             boolean valid = (validEntries.get(name.toLowerCase()) != null);
 280 
 281             if (verify) {
 282                 if (!valid) {
 283                     error(i18n, "expand.unknownEntry",
 284                           new Object[] {name, getCurrentFile()} );
 285                 }
 286                 if (name.equalsIgnoreCase("keywords")) {
 287                     String[] keys = StringArray.split(value);
 288                     if (keys != null) {
 289                         for (int i = 0; i < keys.length; i++) {
 290                             String key = keys[i];
 291                             // minor modification here to allow no keywords
 292                             //if (validKeywords.get(key.toLowerCase()) == null) {
 293                             if ((validKeywords.size() > 0) && (validKeywords.get(key.toLowerCase()) == null)) {
 294                                 error(i18n, "expand.unknownKeyword",
 295                                       new Object[] {key, getCurrentFile()} );
 296                             }
 297                         }
 298                     }
 299                 }
 300             }
 301 
 302             if (valid)
 303                 entries.put(name, value);
 304             // end of code for JCKTagTestFinder emulation
 305 
 306             // if emulation code is removed, still need to do a put
 307             // super.processEntry(entries, name, value);
 308         }
 309     } // processEntry()
 310 
 311     //----------member variables------------------------------------------------
 312 
 313     private static final String TRUE = "true";
 314     private Map<String, String> testStems = null;
 315     private Map<String, String[]> expandVars;
 316     private Map<String, Integer> expandVarLen;
 317 
 318     private String[] stdValidEntries = {
 319         // required
 320         "keywords",
 321         "source",
 322         "title",
 323         // optional
 324         "context",
 325         "executeArgs",
 326         "executeClass",
 327         "executeNative",
 328         "id",           // defined and used internally by JT Harness
 329         "rmicClasses",
 330         "timeout"
 331     };
 332 
 333     private String[] stdValidKeywords = {
 334         // approved
 335         "compiler",
 336         "runtime",
 337         "positive",
 338         "negative",
 339         "idl_inherit",
 340         "idl_tie",
 341         "interactive",
 342         "jniinvocationapi",
 343         "optionalPJava",
 344         // will eventually be superceded/deprecated
 345         "serial"
 346     };
 347     private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(ExpandTestFinder.class);
 348 }