1 /*
   2  * Copyright (c) 2018, 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 
  24 package lib.jdb;
  25 
  26 import jdk.test.lib.compiler.CompilerUtils;
  27 import java.io.IOException;
  28 import java.nio.file.Files;
  29 import java.nio.file.Path;
  30 import java.nio.file.Paths;
  31 import java.text.MessageFormat;
  32 import java.util.Arrays;
  33 import java.util.LinkedList;
  34 import java.util.List;
  35 import java.util.regex.Matcher;
  36 import java.util.regex.Pattern;
  37 import java.util.stream.Collectors;
  38 
  39 // ClassTransformer provides functionality to transform java source and compile it.
  40 // We cannot use InMemoryJavaCompiler as test files usually contain 2 classes (the test itself and debuggee)
  41 // and InMemoryJavaCompiler cannot compile them.
  42 public class ClassTransformer {
  43 
  44     private final List<String> lines;
  45     private String fileName;
  46     private String workDir = "ver{0}";
  47     private static final String LINE_SEPARATOR = System.getProperty("line.separator");
  48 
  49     private ClassTransformer(List<String> lines) {
  50         this.lines = lines;
  51     }
  52 
  53     public ClassTransformer fileName(String fileName) {
  54         this.fileName = fileName;
  55         return this;
  56     }
  57 
  58     // workDir is a MessageFormat pattern, id (int) is an {0} arg of the pattern.
  59     // can be relative (relatively "scratch" dir) or absolute.
  60     public ClassTransformer workDir(String dir) {
  61         workDir = dir;
  62         return this;
  63     }
  64 
  65     public static ClassTransformer fromString(String content) {
  66         return new ClassTransformer(Arrays.asList(content.split("\\R")));
  67     }
  68 
  69     public static ClassTransformer fromFile(Path filePath) {
  70         try {
  71             return new ClassTransformer(Files.readAllLines(filePath))
  72                     .fileName(filePath.getFileName().toString());
  73         } catch (IOException e) {
  74             throw new RuntimeException("failed to read " + filePath, e);
  75         }
  76     }
  77     public static ClassTransformer fromFile(String filePath) {
  78         return fromFile(Paths.get(filePath));
  79     }
  80 
  81     public static ClassTransformer fromTestSource(String fileName) {
  82         return fromFile(Paths.get(System.getProperty("test.src")).resolve(fileName));
  83     }
  84 
  85     // returns path to the .class file of the transformed class
  86     public String transform(int id, String className, String... compilerOptions) {
  87         Path subdir = Paths.get(".").resolve(MessageFormat.format(workDir, id));
  88         Path transformedSrc = subdir.resolve(fileName);
  89         try {
  90             Files.createDirectories(subdir);
  91             Files.write(transformedSrc, transform(id).getBytes());
  92         } catch (IOException e) {
  93             throw new RuntimeException("failed to write transformed " + transformedSrc, e);
  94         }
  95         try {
  96             // need to add extra classpath args
  97             List<String> args = new LinkedList<>(Arrays.asList(compilerOptions));
  98             args.add("-cp");
  99             args.add(System.getProperty("java.class.path"));
 100             CompilerUtils.compile(subdir, subdir, false, args.toArray(new String[args.size()]));
 101         } catch (IOException e) {
 102             throw new RuntimeException("failed to compile " + transformedSrc, e);
 103         }
 104         return subdir.resolve(className + ".class").toString();
 105     }
 106 
 107     /*
 108     To do RedefineClasses operations, embed @1 tags in the .java
 109     file to tell this script how to modify it to produce the 2nd
 110     version of the .class file to be used in the redefine operation.
 111     Here are examples of each editing tag and what change
 112     it causes in the new file.  Note that blanks are not preserved
 113     in these editing operations.
 114 
 115     @1 uncomment
 116      orig:   // @1 uncomment   gus = 89;
 117      new:         gus = 89;
 118 
 119     @1 commentout
 120      orig:   gus = 89      // @1 commentout
 121      new: // gus = 89      // @1 commentout
 122 
 123     @1 delete
 124      orig:  gus = 89      // @1 delete
 125      new:   entire line deleted
 126 
 127     @1 newline
 128      orig:  gus = 89;     // @1 newline gus++;
 129      new:   gus = 89;     //
 130             gus++;
 131 
 132     @1 replace
 133      orig:  gus = 89;     // @1 replace gus = 90;
 134      new:   gus = 90;
 135     */
 136     public String transform(int id) {
 137         Pattern delete = Pattern.compile("@" + id + " *delete");
 138         Pattern uncomment = Pattern.compile("// *@" + id + " *uncomment (.*)");
 139         Pattern commentout = Pattern.compile(".* @" + id + " *commentout");
 140         Pattern newline = Pattern.compile("(.*) @" + id + " *newline (.*)");
 141         Pattern replace = Pattern.compile("@" + id + " *replace (.*)");
 142         return lines.stream()
 143                 .filter(s -> !delete.matcher(s).find())     // @1 delete
 144                 .map(s -> {
 145                     Matcher m = uncomment.matcher(s);       // @1 uncomment
 146                     return m.find() ? m.group(1) : s;
 147                 })
 148                 .map(s-> {
 149                     Matcher m = commentout.matcher(s);      // @1 commentout
 150                     return m.find() ? "//" + s : s;
 151                 })
 152                 .map(s -> {
 153                     Matcher m = newline.matcher(s);         // @1 newline
 154                     return m.find() ? m.group(1) + LINE_SEPARATOR + m.group(2) : s;
 155                 })
 156                 .map(s -> {
 157                     Matcher m = replace.matcher(s);         // @1 replace
 158                     return m.find() ? m.group(1) : s;
 159                 })
 160                 .collect(Collectors.joining(LINE_SEPARATOR));
 161     }
 162 
 163 }