cc.arduino.packages.discoverers.PluggableDiscovery.java Source code

Java tutorial

Introduction

Here is the source code for cc.arduino.packages.discoverers.PluggableDiscovery.java

Source

/*
 * This file is part of Arduino.
 *
 * Arduino is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * As a special exception, you may use this file as part of a free software
 * library without restriction.  Specifically, if other files instantiate
 * templates or use macros or inline functions from this file, or you compile
 * this file and link it with other files to produce an executable, this
 * file does not by itself cause the resulting executable to be covered by
 * the GNU General Public License.  This exception does not however
 * invalidate any other reasons why the executable file might be covered by
 * the GNU General Public License.
 *
 * Copyright 2018 Arduino SA (http://www.arduino.cc/)
 */

package cc.arduino.packages.discoverers;

import static processing.app.I18n.format;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import cc.arduino.packages.BoardPort;
import cc.arduino.packages.Discovery;
import processing.app.PreferencesData;

public class PluggableDiscovery implements Discovery {

    private final String discoveryName;
    private final String[] cmd;
    private final List<BoardPort> portList = new ArrayList<>();
    private Process program = null;
    private Thread pollingThread;

    private void debug(String x) {
        if (PreferencesData.getBoolean("discovery.debug"))
            System.out.println(discoveryName + ": " + x);
    }

    public PluggableDiscovery(String discoveryName, String[] cmd) {
        this.cmd = cmd;
        this.discoveryName = discoveryName;
    }

    @Override
    public void run() {
        // this method is started as a new thread, it will constantly listen
        // to the discovery tool and keep track of the discovered ports
        try {
            start();
            InputStream input = program.getInputStream();
            JsonFactory factory = new JsonFactory();
            JsonParser parser = factory.createParser(input);
            ObjectMapper mapper = new ObjectMapper();
            mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
            mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

            while (program != null && program.isAlive()) {
                JsonNode tree = mapper.readTree(parser);
                if (tree == null) {
                    if (program != null && program.isAlive()) {
                        System.err.println(format("{0}: Invalid json message", discoveryName));
                    }
                    break;
                }
                debug("Received json: " + tree);

                processJsonNode(mapper, tree);
            }
            debug("thread exit normally");
        } catch (InterruptedException e) {
            debug("thread exit by interrupt");
            e.printStackTrace();
        } catch (Exception e) {
            debug("thread exit other exception");
            e.printStackTrace();
        }
        try {
            stop();
        } catch (Exception e) {
        }
    }

    private void processJsonNode(ObjectMapper mapper, JsonNode node) {
        JsonNode eventTypeNode = node.get("eventType");
        if (eventTypeNode == null) {
            System.err.println(format("{0}: Invalid message, missing eventType", discoveryName));
            return;
        }

        switch (eventTypeNode.asText()) {
        case "error":
            try {
                PluggableDiscoveryMessage msg = mapper.treeToValue(node, PluggableDiscoveryMessage.class);
                debug("error: " + msg.getMessage());
                if (msg.getMessage().contains("START_SYNC")) {
                    startPolling();
                }
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return;

        case "list":
            JsonNode portsNode = node.get("ports");
            if (portsNode == null) {
                System.err.println(format("{0}: Invalid message, missing ports list", discoveryName));
                return;
            }
            if (!portsNode.isArray()) {
                System.err.println(format("{0}: Invalid message, ports list should be an array", discoveryName));
                return;
            }

            synchronized (portList) {
                portList.clear();
            }
            portsNode.forEach(portNode -> {
                BoardPort port = mapJsonNodeToBoardPort(mapper, node);
                if (port != null) {
                    addOrUpdate(port);
                }
            });
            return;

        // Messages for SYNC updates

        case "add":
            BoardPort addedPort = mapJsonNodeToBoardPort(mapper, node);
            if (addedPort != null) {
                addOrUpdate(addedPort);
            }
            return;

        case "remove":
            BoardPort removedPort = mapJsonNodeToBoardPort(mapper, node);
            if (removedPort != null) {
                remove(removedPort);
            }
            return;

        default:
            debug("Invalid event: " + eventTypeNode.asText());
            return;
        }
    }

    private BoardPort mapJsonNodeToBoardPort(ObjectMapper mapper, JsonNode node) {
        try {
            BoardPort port = mapper.treeToValue(node.get("port"), BoardPort.class);
            // if no label, use address
            if (port.getLabel() == null || port.getLabel().isEmpty()) {
                port.setLabel(port.getAddress());
            }
            port.searchMatchingBoard();
            return port;
        } catch (JsonProcessingException e) {
            System.err.println(format("{0}: Invalid BoardPort message", discoveryName));
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void start() throws Exception {
        try {
            debug("Starting: " + StringUtils.join(cmd, " "));
            program = Runtime.getRuntime().exec(cmd);
        } catch (Exception e) {
            program = null;
            return;
        }
        debug("START_SYNC");
        write("START_SYNC\n");
        pollingThread = null;
    }

    private void startPolling() {
        // Discovery tools not supporting START_SYNC require a periodic
        // LIST command.  A second thread is created to send these
        // commands, while the run() thread above listens for the
        // discovery tool output.
        debug("START");
        write("START\n");
        Thread pollingThread = new Thread() {
            public void run() {
                try {
                    while (program != null && program.isAlive()) {
                        debug("LIST");
                        write("LIST\n");
                        sleep(2500);
                    }
                } catch (Exception e) {
                }
            }
        };
        pollingThread.start();
    }

    @Override
    public void stop() throws Exception {
        if (pollingThread != null) {
            pollingThread.interrupt();
            pollingThread = null;
        }
        write("STOP\n");
        if (program != null) {
            program.destroy();
            program = null;
        }
    }

    private void write(String command) {
        if (program != null && program.isAlive()) {
            OutputStream out = program.getOutputStream();
            try {
                out.write(command.getBytes());
                out.flush();
            } catch (Exception e) {
            }
        }
    }

    private void addOrUpdate(BoardPort port) {
        String address = port.getAddress();
        if (address == null)
            return; // address required for "add" & "remove"

        synchronized (portList) {
            // if address already on the list, discard old info
            portList.removeIf(bp -> address.equals(bp.getAddress()));
            portList.add(port);
        }
    }

    private void remove(BoardPort port) {
        String address = port.getAddress();
        if (address == null)
            return; // address required for "add" & "remove"
        synchronized (portList) {
            portList.removeIf(bp -> address.equals(bp.getAddress()));
        }
    }

    @Override
    public List<BoardPort> listDiscoveredBoards() {
        synchronized (portList) {
            return new ArrayList<>(portList);
        }
    }

    @Override
    public List<BoardPort> listDiscoveredBoards(boolean complete) {
        // XXX: parameter "complete "is really needed?
        // should be checked on all existing discoveries
        synchronized (portList) {
            return new ArrayList<>(portList);
        }
    }

    @Override
    public String toString() {
        return discoveryName;
    }
}