HistoryObject.java revision 1695:be28ce2f1054
1/*
2 * Copyright (c) 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.  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
26package jdk.nashorn.tools.jjs;
27
28import java.io.BufferedReader;
29import java.io.File;
30import java.io.FileReader;
31import java.io.IOException;
32import java.io.PrintWriter;
33import java.util.Collections;
34import java.util.HashSet;
35import java.util.Set;
36import java.util.function.Consumer;
37import java.util.function.Function;
38import java.util.function.Supplier;
39import jdk.internal.jline.console.history.History;
40import jdk.nashorn.api.scripting.AbstractJSObject;
41import jdk.nashorn.api.scripting.JSObject;
42import jdk.nashorn.internal.runtime.JSType;
43import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
44import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
45
46/*
47 * A script friendly object that exposes history of commands to scripts.
48 */
49final class HistoryObject extends AbstractJSObject {
50    private static final Set<String> props;
51    static {
52        final HashSet<String> s = new HashSet<>();
53        s.add("clear");
54        s.add("forEach");
55        s.add("load");
56        s.add("print");
57        s.add("save");
58        s.add("size");
59        s.add("toString");
60        props = Collections.unmodifiableSet(s);
61    }
62
63    private final History hist;
64    private final PrintWriter err;
65    private final Consumer<String> evaluator;
66
67    HistoryObject(final History hist, final PrintWriter err,
68            final Consumer<String> evaluator) {
69        this.hist = hist;
70        this.err = err;
71        this.evaluator = evaluator;
72    }
73
74    @Override
75    public boolean isFunction() {
76        return true;
77    }
78
79    @Override
80    public Object call(final Object thiz, final Object... args) {
81        if (args.length > 0) {
82            int index = JSType.toInteger(args[0]);
83            if (index < 0) {
84                index += (hist.size() - 1);
85            } else {
86                index--;
87            }
88
89            if (index >= 0 && index < (hist.size() - 1)) {
90                final CharSequence src = hist.get(index);
91                hist.replace(src);
92                err.println(src);
93                evaluator.accept(src.toString());
94            } else {
95                hist.removeLast();
96                err.println("no history entry @ " + (index + 1));
97            }
98        }
99        return UNDEFINED;
100    }
101
102    @Override
103    public Object getMember(final String name) {
104        switch (name) {
105            case "clear":
106                return (Runnable)hist::clear;
107            case "forEach":
108                return (Function<JSObject, Object>)this::iterate;
109            case "load":
110                return (Consumer<Object>)this::load;
111            case "print":
112                return (Runnable)this::print;
113            case "save":
114                return (Consumer<Object>)this::save;
115            case "size":
116                return hist.size();
117            case "toString":
118                return (Supplier<String>)this::toString;
119        }
120        return UNDEFINED;
121    }
122
123    @Override
124    public Object getDefaultValue(final Class<?> hint) {
125        if (hint == String.class) {
126            return toString();
127        }
128        return UNDEFINED;
129    }
130
131    @Override
132    public String toString() {
133        final StringBuilder buf = new StringBuilder();
134        for (History.Entry e : hist) {
135            buf.append(e.value()).append('\n');
136        }
137        return buf.toString();
138    }
139
140    @Override
141    public Set<String> keySet() {
142        return props;
143    }
144
145    private void save(final Object obj) {
146        final File file = getFile(obj);
147        try (final PrintWriter pw = new PrintWriter(file)) {
148            for (History.Entry e : hist) {
149                pw.println(e.value());
150            }
151        } catch (final IOException exp) {
152            throw new RuntimeException(exp);
153        }
154    }
155
156    private void load(final Object obj) {
157        final File file = getFile(obj);
158        String item = null;
159        try (final BufferedReader r = new BufferedReader(new FileReader(file))) {
160            while ((item = r.readLine()) != null) {
161                hist.add(item);
162            }
163        } catch (final IOException exp) {
164            throw new RuntimeException(exp);
165        }
166    }
167
168    private void print() {
169        for (History.Entry e : hist) {
170            System.out.printf("%3d %s\n", e.index() + 1, e.value());
171        }
172    }
173
174    private Object iterate(final JSObject func) {
175        for (History.Entry e : hist) {
176            if (JSType.toBoolean(func.call(this, e.value().toString()))) {
177                break; // return true from callback to skip iteration
178            }
179        }
180        return UNDEFINED;
181    }
182
183    private static File getFile(final Object obj) {
184        File file = null;
185        if (obj instanceof String) {
186            file = new File((String)obj);
187        } else if (obj instanceof File) {
188            file = (File)obj;
189        } else {
190            throw typeError("not.a.file", JSType.toString(obj));
191        }
192
193        return file;
194    }
195}
196