blob: 524ee357f8f2954d85ad26f7557fa833b4e6f24c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.jshell.support;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import jdk.jshell.Snippet;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
/**
* File-based implementation of saved history.
*
* @author sdedic
*/
public class FileHistory implements ShellHistory {
private static final Logger LOG = Logger.getLogger(FileHistory.class.getName());
private static final int MAX_HISTORY_ITEMS = 50;
private FileObject historyFile;
private List<ChangeListener> listeners = new ArrayList<>();
private List<Item> items;
private boolean fileWarned;
private int maxHistory = MAX_HISTORY_ITEMS;
protected FileHistory(FileObject historyFile) {
this.historyFile = historyFile;
}
protected void setMaxHistoryItems(int max) {
this.maxHistory = max;
}
@Override
public List<Item> getHistory() {
load();
return items;
}
private String item2String(Item item) {
String k;
if (item.isShellCommand()) {
k = KIND_COMMAND; // NOI18N
} else {
k = item.getKind().name().toUpperCase();
}
return k + MARKER+ item.getContents();
}
private Item string2Item(String s) {
int marker = s.indexOf(MARKER);
if (marker == -1) {
if (s.isEmpty()) {
return null;
}
if (s.charAt(0) == '/') {
return new Item(null, true, s);
} else {
return new Item(Snippet.Kind.ERRONEOUS, false, s);
}
}
String k = s.substring(0, marker);
String c = s.substring(marker + MARKER.length());
if (KIND_COMMAND.equals(k)) {
return new Item(null, true, c);
}
Snippet.Kind kind;
try {
kind = Snippet.Kind.valueOf(k);
} catch (IllegalArgumentException ex) {
kind = Snippet.Kind.ERRONEOUS;
}
return new Item(kind, false, c);
}
private static final String MARKER = "#>";
private static final String KIND_COMMAND = "CMD";
@Override
public void pushItems(List<Item> newItems) {
load();
synchronized (this) {
items.addAll(newItems);
if (items.size() > maxHistory) {
items.removeAll(items.subList(0, items.size() - maxHistory));
}
}
ChangeListener[] ll = null;
synchronized (this) {
if (!listeners.isEmpty()) {
ll = newItems.toArray(new ChangeListener[newItems.size()]);
}
}
if (ll != null) {
ChangeEvent e = new ChangeEvent(this);
for (ChangeListener l : ll) {
l.stateChanged(e);
}
}
save();
}
@Override
public void addChangeListener(ChangeListener l) {
synchronized (this) {
if (!listeners.contains(l)) {
listeners.add(l);
}
}
}
@Override
public void removeChangeListener(ChangeListener l) {
synchronized (this) {
listeners.remove(l);
}
}
@Override
public void clear() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
protected FileObject createFile() throws IOException {
return null;
}
private void load() {
synchronized (this) {
if (items != null) {
return;
}
}
if (historyFile == null) {
items = new ArrayList<>();
return;
}
List<Item> loadedItems = new ArrayList<>();
try {
StringBuilder sb = new StringBuilder();
boolean continuation = false;
for (String s : historyFile.asLines()) {
int from = 0;
if (continuation) {
// eat leading tab, if present:
if (!s.isEmpty() && s.charAt(0) == '\t') {
s = s.substring(1);
}
}
continuation = (!s.isEmpty() && s.charAt(s.length() - 1) == '\\');
if (!continuation) {
sb.append(s);
loadedItems.add(string2Item(sb.toString()));
sb = new StringBuilder();
} else {
sb.append(s, 0, s.length() - 1);
sb.append("\n");
}
}
if (continuation) {
loadedItems.add(string2Item(sb.toString()));
}
} catch (IOException ex) {
Exceptions.printStackTrace(
Exceptions.attachMessage(
Exceptions.attachSeverity(ex, Level.INFO),
"Unable to load shell history"
));
}
synchronized (this) {
if (this.items == null) {
items = loadedItems;
}
}
}
private void save() {
try {
load();
if (historyFile == null) {
historyFile = createFile();
}
} catch (IOException ex) {
LOG.log(Level.INFO, "Unable to create history file",
Exceptions.attachSeverity(ex, Level.INFO));
return;
}
List<Item> itemsToSave;
synchronized (this) {
itemsToSave = this.items;
}
try (BufferedWriter wr = new BufferedWriter(
new OutputStreamWriter(historyFile.getOutputStream(), "UTF-8"))) {
for (Item item : itemsToSave) {
String s = item2String(item);
String[] lines = s.split("\n");
int count = lines.length;
for (String l : lines) {
l.trim();
wr.write(l);
if (count != 1) {
// indent the following line
wr.write("\\");
wr.newLine();
wr.write("\t");
} else {
wr.newLine();
}
count--;
}
}
wr.flush();
} catch (IOException ex) {
if (!fileWarned) {
LOG.log(Level.INFO, "Unable to write history file",
Exceptions.attachSeverity(ex, Level.INFO));
fileWarned = true;
}
}
}
}