/******************************************************************************* * Copyright (c) 2006 Vlad Dumitrescu and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Vlad Dumitrescu *******************************************************************************/ package org.erlide.backend.console; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.erlide.backend.api.IBackend; import org.erlide.runtime.events.ErlangEventHandler; import org.erlide.runtime.shell.BackendShellEvent; import org.erlide.runtime.shell.BackendShellListener; import org.erlide.runtime.shell.IBackendShell; import org.erlide.runtime.shell.IoRequest; import org.erlide.runtime.shell.IoRequest.IoRequestKind; import org.erlide.util.ErlLogger; import org.erlide.util.erlang.OtpErlang; import com.ericsson.otp.erlang.OtpErlangAtom; import com.ericsson.otp.erlang.OtpErlangObject; import com.ericsson.otp.erlang.OtpErlangPid; import com.ericsson.otp.erlang.OtpErlangString; import com.ericsson.otp.erlang.OtpErlangTuple; import com.google.common.collect.Lists; public class BackendShell implements IBackendShell { private final IBackend backend; private OtpErlangPid server; private final String fId; public BackendShell(final IBackend backend, final String id, final OtpErlangPid server) { this.backend = backend; fId = id; this.server = server; requests = new ArrayList<>(1000); listeners = new ArrayList<>(); } @Override public void open() { final ErlangEventHandler handler = new ConsoleEventHandler(this); backend.getRuntime().registerEventListener(handler); } @Override public void close() { if (server != null && backend.getOtpRpc() != null) { backend.getOtpRpc().send(server, new OtpErlangAtom("stop")); } server = null; } @Override public void send(final String string) { if (server != null) { backend.getOtpRpc().send(server, OtpErlang.mkTuple(new OtpErlangAtom("input"), new OtpErlangString(string))); } else { try { backend.input(string); } catch (final IOException e) { ErlLogger.error(e); } } } @Override public String getId() { return fId; } private static final int MAX_REQUESTS = 5000; private static final int DELTA_REQUESTS = 500; private final List<IoRequest> requests; private final List<BackendShellListener> listeners; private int length = 0; @Override public void input(final String s0) { String s = s0; if (!s.endsWith("\n")) { s += "\n"; } final IoRequest request = new IoRequest(s, IoRequestKind.INPUT); request.setStart(length); final int prevLength = length; addRequest(request); notifyListeners(makeEvent(prevLength, request)); } private void addRequest(final IoRequest req) { final int reqLength = req.getLength(); length += reqLength; synchronized (requests) { requests.add(req); } } @Override public void add(final OtpErlangObject msg) { deleteOldItems(); final int prevLength = length; final IoRequest request = addRequestFromTuple(msg); if (request != null) { notifyListeners(makeEvent(prevLength, request)); } } @Override public void add(final String text, final IoRequestKind kind) { if (IoRequest.RE_PROMPT.matcher(text).matches()) { return; } final IoRequest request = new IoRequest(text, kind); request.setStart(length); final int prevLength = length; addRequest(request); notifyListeners(makeEvent(prevLength, request)); } private BackendShellEvent makeEvent(final int prevLength, final IoRequest request) { return new BackendShellEvent(prevLength, 0, request.getMessage()); } private IoRequest addRequestFromTuple(final OtpErlangObject msg) { if (!(msg instanceof OtpErlangTuple)) { return null; } final IoRequest request = new IoRequest((OtpErlangTuple) msg); request.setStart(length); addRequest(request); return request; } private void deleteOldItems() { final int prevLength = length; synchronized (requests) { if (requests.size() > MAX_REQUESTS) { requests.subList(0, DELTA_REQUESTS).clear(); final IoRequest first = requests.get(0); final int start = first.getStart(); length = 0; for (final IoRequest request : requests) { request.setStart(request.getStart() - start); length += request.getLength(); } } } if (length != prevLength) { notifyListeners(new BackendShellEvent(0, length - prevLength, "")); } } @Override public IoRequest findAtPos(final int thePos) { synchronized (requests) { for (final IoRequest req : requests) { if (req.getStart() <= thePos && req.getStart() + req.getLength() > thePos) { return req; } } return null; } } @Override public List<IoRequest> getAllFrom(final OtpErlangPid sender) { final List<IoRequest> result = new ArrayList<>(); synchronized (requests) { for (final IoRequest element : requests) { if (element.getSender().equals(sender)) { result.add(element); } } } return result; } @Override public void add(final List<OtpErlangObject> msgs) { final int prevLength = length; final StringBuffer text = new StringBuffer(); synchronized (requests) { deleteOldItems(); for (final OtpErlangObject element : msgs) { final IoRequest request = addRequestFromTuple(element); if (request != null) { requests.add(request); text.append(request.getMessage()); } } } if (!requests.isEmpty()) { notifyListeners(new BackendShellEvent(prevLength, 0, text.toString())); } } @Override public void dispose() { listeners.clear(); } @Override public synchronized void addListener(final BackendShellListener listener) { sendEarlierRequests(listener); synchronized (listeners) { if (!listeners.contains(listener)) { listeners.add(listener); } } } private void sendEarlierRequests(final BackendShellListener listener) { int alength = 0; synchronized (requests) { for (final IoRequest request : requests) { listener.changed(makeEvent(alength, request)); alength += request.getLength(); } } } @Override public void removeListener(final BackendShellListener listener) { synchronized (listeners) { listeners.remove(listener); } } private void notifyListeners(final BackendShellEvent event) { final List<BackendShellListener> listenersCopy; synchronized (listeners) { listenersCopy = Lists.newArrayList(listeners); } for (final BackendShellListener listener : listenersCopy) { listener.changed(event); } } @Override public int getTextLength() { return length; } @Override public String getText() { final StringBuffer result = new StringBuffer(); synchronized (requests) { for (final IoRequest request : requests) { result.append(request.getMessage()); } } return result.toString(); } @Override public String[] getLastMessages(final int nMessages) { final List<String> result = Lists.newArrayListWithCapacity(nMessages); synchronized (requests) { final int size = requests.size(); final int n = Math.min(nMessages, size); for (int i = size - n; i < size; ++i) { result.add(requests.get(i).getMessage()); } } return result.toArray(new String[nMessages]); } }