1 /* 2 * Copyright (c) 2015, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.tools.jjs; 27 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.FileReader; 31 import java.io.IOException; 32 import java.io.PrintWriter; 33 import java.io.UncheckedIOException; 34 import java.util.Collections; 35 import java.util.HashSet; 36 import java.util.Set; 37 import java.util.function.Consumer; 38 import java.util.function.Function; 39 import java.util.function.Supplier; 40 41 import jdk.internal.org.jline.reader.History; 42 import jdk.nashorn.api.scripting.AbstractJSObject; 43 import jdk.nashorn.api.scripting.JSObject; 44 import jdk.nashorn.internal.runtime.JSType; 45 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 46 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 47 48 /* 49 * A script friendly object that exposes history of commands to scripts. 50 */ 51 final class HistoryObject extends AbstractJSObject { 52 private static final Set<String> props; 53 static { 54 final HashSet<String> s = new HashSet<>(); 55 s.add("clear"); 56 s.add("forEach"); 57 s.add("load"); 58 s.add("print"); 59 s.add("save"); 60 s.add("size"); 61 s.add("toString"); 62 props = Collections.unmodifiableSet(s); 63 } 64 65 private final History hist; 66 private final PrintWriter err; 67 private final Consumer<String> evaluator; 68 69 HistoryObject(final History hist, final PrintWriter err, 70 final Consumer<String> evaluator) { 71 this.hist = hist; 72 this.err = err; 73 this.evaluator = evaluator; 74 } 75 76 @Override 77 public boolean isFunction() { 78 return true; 79 } 80 81 @Override 82 public Object call(final Object thiz, final Object... args) { 83 if (args.length > 0) { 84 int index = JSType.toInteger(args[0]); 85 if (index < 0) { 86 index += (hist.size() - 1); 87 } else { 88 index--; 89 } 90 91 if (index >= 0 && index < (hist.size() - 1)) { 92 final CharSequence src = hist.get(index); 93 var it = hist.iterator(); 94 while (it.hasNext()) { 95 it.next(); 96 } 97 it.remove(); 98 hist.add(src.toString()); 99 err.println(src); 100 evaluator.accept(src.toString()); 101 } else { 102 var it = hist.iterator(); 103 while (it.hasNext()) { 104 it.next(); 105 } 106 it.remove(); 107 err.println("no history entry @ " + (index + 1)); 108 } 109 } 110 return UNDEFINED; 111 } 112 113 @Override 114 public Object getMember(final String name) { 115 switch (name) { 116 case "clear": 117 return (Runnable) () -> { 118 try { 119 hist.purge(); 120 } catch (IOException ex) { 121 throw new UncheckedIOException(ex); 122 } 123 }; 124 case "forEach": 125 return (Function<JSObject, Object>)this::iterate; 126 case "load": 127 return (Consumer<Object>)this::load; 128 case "print": 129 return (Runnable)this::print; 130 case "save": 131 return (Consumer<Object>)this::save; 132 case "size": 133 return hist.size(); 134 case "toString": 135 return (Supplier<String>)this::toString; 136 } 137 return UNDEFINED; 138 } 139 140 @Override 141 public Object getDefaultValue(final Class<?> hint) { 142 if (hint == String.class) { 143 return toString(); 144 } 145 return UNDEFINED; 146 } 147 148 @Override 149 public String toString() { 150 final StringBuilder buf = new StringBuilder(); 151 for (History.Entry e : hist) { 152 buf.append(e.line()).append('\n'); 153 } 154 return buf.toString(); 155 } 156 157 @Override 158 public Set<String> keySet() { 159 return props; 160 } 161 162 private void save(final Object obj) { 163 final File file = getFile(obj); 164 try (final PrintWriter pw = new PrintWriter(file)) { 165 for (History.Entry e : hist) { 166 pw.println(e.line()); 167 } 168 } catch (final IOException exp) { 169 throw new RuntimeException(exp); 170 } 171 } 172 173 private void load(final Object obj) { 174 final File file = getFile(obj); 175 String item = null; 176 try (final BufferedReader r = new BufferedReader(new FileReader(file))) { 177 while ((item = r.readLine()) != null) { 178 hist.add(item); 179 } 180 } catch (final IOException exp) { 181 throw new RuntimeException(exp); 182 } 183 } 184 185 private void print() { 186 for (History.Entry e : hist) { 187 System.out.printf("%3d %s\n", e.index() + 1, e.line()); 188 } 189 } 190 191 private Object iterate(final JSObject func) { 192 for (History.Entry e : hist) { 193 if (JSType.toBoolean(func.call(this, e.line().toString()))) { 194 break; // return true from callback to skip iteration 195 } 196 } 197 return UNDEFINED; 198 } 199 200 private static File getFile(final Object obj) { 201 File file = null; 202 if (obj instanceof String) { 203 file = new File((String)obj); 204 } else if (obj instanceof File) { 205 file = (File)obj; 206 } else { 207 throw typeError("not.a.file", JSType.toString(obj)); 208 } 209 210 return file; 211 } 212 }