text:
// this encapsulates the system under emulation.

#include "CardInfo.h"
#include "Cpu2200.h"
#include "IoCardDisk.h"
#include "IoCardKeyboard.h"  // for KEYCODE_HALT
#include "Scheduler.h"
#include "ScriptFile.h"
#include "SysCfgState.h"
#include "Ui.h"
#include "host.h"
#include "system2200.h"

#include <algorithm>
#include <sstream>

// ----------------------------------------------------------------------------
// this is the "private" state of the system2200 namespace,
// invisible to anyone importing system2200.h
// ----------------------------------------------------------------------------

// for coordinating events in the emulator
static std::shared_ptr<Scheduler> scheduler = nullptr;

// the central processing unit
static std::shared_ptr<Cpu2200> cpu = nullptr;

// active system configuration
static std::shared_ptr<SysCfgState> current_cfg = nullptr;

// ------------------------------- I/O dispatch -------------------------------

// mapping i/o addresses to devices
struct iomap_t {
    int  slot;      // slot number of the device which "owns" this address
    bool ignore;    // if false, access generates a warning message if ...
                    // ... there is no device at that address
};

// pointer to card in a given slot
static std::array<std::unique_ptr<IoCard>, NUM_IOSLOTS> card_in_slot;

// pointer to card responding to given address
static std::array<iomap_t, 256> ioMap;

// address of most recent ABS
static int curIoAddr;

// ----------------------------- speed regulation -----------------------------

static bool  first_slice    = false; // has realtime_start been initialized?
static int64 realtime_start = 0;     // relative wall time of when sim started
static int   real_seconds   = 0;     // real time elapsed
static uint32 sim_seconds   = 0;     // number of actual seconds simulated time

// amount of actual simulated time elapsed, in ms
static int64 sim_time_ns;

// amount of adjusted simulated time elapsed, in ms because of user events
// (eg, menu selection), sim time is often interrupted and we don't
// actually desire to catch up. sim_time_ns is the actual number of
// simulated slices, while this var has been fudged to account for those
// violations of realtime.
static int64 adjust_sim_time;

// keep a rolling average of how fast we are running in case it is reported.
// we want to report the running average over the last second of realtime
// to prevent the average from updating like crazy when running many times
// realtime.
static const int perf_hist_size = 100;  // # of timeslices to track
static       int perf_hist_len  = 0;    // number of entries written
static       int perf_hist_ptr  = 0;    // next entry to write
static     int64 perf_real_ms[100];     // realtime at start of each slice

// things which get called as time advances. it is used by the
// core 2200 CPU and any peripheral which uses a microprocessor.
// each device has a ns resolution counter, but we keep rebasing
// the count so the counter doesn't overflow.  all we care is the
// difference between the devices' sense of time.
struct clocked_device_t {
    clkCallback callback_fn;
    uint32      ns;          // nanoseconds
};
static std::vector<clocked_device_t> m_clocked_devices;

// ----------------------- keyboard input routing table -----------------------

struct kb_route_t {
    int         io_addr;
    int         term_num;       // 0..3 for smart terms; 0 for display controllers
    kbCallback  callback_fn;
    // can't use unique_ptr because it doesn't allow copy assignment
    // (only move) and std::vector requires copy assignment.
    std::shared_ptr<ScriptFile> script_handle;
};

static std::vector<kb_route_t> keyboard_routes;

// ----------------------------------------------------------------------------
// initialize static class members
// ----------------------------------------------------------------------------

// help ensure an orderly shutdown
enum term_state_t {
    RUNNING,
    TERMINATING,
    TERMINATED
};
static term_state_t m_termination_state = RUNNING;

static bool m_freeze_emu  = false;  // toggle to prevent time advancing
static bool m_do_reconfig = false;  // deferred request to reconfigure

static void
setTerminationState(term_state_t newstate) noexcept
{
    m_termination_state = newstate;
}

static term_state_t
getTerminationState() noexcept
{
    return m_termination_state;
}

// ----------------------------------------------------------------------------
// save/restore state to/from ini file
// ----------------------------------------------------------------------------

// returns true if the slot contains a disk controller, 0 otherwise
static bool
isDiskController(int slot) noexcept
{
    assert(slot >= 0 && slot < NUM_IOSLOTS);

    int cardtype_idx;
    const bool ok = system2200::getSlotInfo(slot, &cardtype_idx, nullptr);

    return ok && (cardtype_idx == static_cast<int>(IoCard::card_t::disk));
}

// for all disk drives, save what is mounted in them (or not)
static void
saveDiskMounts()
{
    for (int slot=0; slot < NUM_IOSLOTS; slot++) {
        if (isDiskController(slot)) {
            std::ostringstream subgroup;
            subgroup << "io/slot-" << slot;
            const auto cfg = current_cfg->getCardConfig(slot);
            const auto dcfg = dynamic_cast<const DiskCtrlCfgState*>(cfg.get());
            assert(dcfg);
            const int num_drives = dcfg->getNumDrives();
            for (int drive=0; drive < num_drives; drive++) {
                std::ostringstream item;
                item << "filename-" << drive;
                std::string filename;
                if (isDiskController(slot)) {
                    const int stat = IoCardDisk::wvdDriveStatus(slot, drive);
                    if ((stat & IoCardDisk::WVD_STAT_DRIVE_OCCUPIED) != 0) {
                        const bool ok = IoCardDisk::wvdGetFilename(slot, drive, &filename);
                        assert(ok);
                    }
                }
                host::configWriteStr(subgroup.str(), item.str(), filename);
            } // drive
        } // if (isDiskController)
    } // slot
}

// remount all disks
static void
restoreDiskMounts()
{
    // look for disk controllers and populate drives
    for (int slot=0; slot < NUM_IOSLOTS; slot++) {
        if (isDiskController(slot)) {
            const auto cfg = current_cfg->getCardConfig(slot);
            const auto dcfg = dynamic_cast<const DiskCtrlCfgState*>(cfg.get());
            assert(dcfg);
            const int num_drives = dcfg->getNumDrives();
            for (int drive=0; drive < num_drives; drive++) {
                std::ostringstream subgroup;
                subgroup << "io/slot-" << slot;
                std::ostringstream item;
                item << "filename-" << drive;
                std::string filename;
                bool b = host::configReadStr(subgroup.str(), item.str(), &filename);
                if (b && !filename.empty()) {
                    IoCardDisk::wvdInsertDisk(slot, drive, filename);
                }
            } // for (drive)
        } // if (isDiskController)
    } // for (slot)
}

// break down any resources currently committed
static void
breakDownCards() noexcept
{
    // destroy card instances
    for (auto &card : card_in_slot) {
        card = nullptr;
    }

    // clean up mappings
    for (auto &mapentry : ioMap) {
        mapentry.slot   = -1;      // unoccupied
        mapentry.ignore = false;   // restore bad I/O warning flags
    }

    if (cpu) {
        cpu->setDevRdy(false);  // nobody is driving, so it floats to 0
    }

    curIoAddr = -1;
}

// ------------------------------------------------------------------------
// "public" members
// ------------------------------------------------------------------------

// build the world
void
system2200::initialize()
{
    // set up IO management
    for (auto &mapentry : ioMap) {
        mapentry.slot   = -1;    // unoccupied
        mapentry.ignore = false;
    }
    for (auto &card : card_in_slot) {
        card = nullptr;
    }
    curIoAddr = -1;

    // CPU speed regulation
    first_slice = true;
    sim_time_ns = adjust_sim_time = host::getTimeMs();
    sim_seconds = 0;

    realtime_start = 0;  // wall time of when sim started
    real_seconds  = 0;   // real time elapsed

    m_do_reconfig = false;
    freezeEmu(false);
    setTerminationState(RUNNING);

    scheduler = std::make_shared<Scheduler>();

    // attempt to load configuration from saved state
    SysCfgState ini_cfg;
    ini_cfg.loadIni();
    if (!ini_cfg.configOk(false)) {
        UI_warn(".ini file wasn't usable -- using a default configuration");
        ini_cfg.setDefaults();
    }
    setConfig(ini_cfg);

#if 0
    // intentional error, to see if the leak checker finds it
    int *leaker = new int[100];
    leaker[1] = 123;
#endif
}

// because everything is static, the destructor does nothing, so we
// need this function to know when the real Armageddon has arrived.
void
system2200::cleanup()
{
    saveDiskMounts();
    breakDownCards();

    cpu       = nullptr;
    scheduler = nullptr;

    current_cfg->saveIni();  // save state to ini file
    current_cfg = nullptr;
}

// called from CrtFrame to signal we want to end things.
// as such, at this point nothing has been shut down.
// after all windows have been shut down, we receive OnExit().
void
system2200::terminate() noexcept
{
    setTerminationState(TERMINATING);
}

// unregister a callback function which advances with the clock
void
system2200::registerClockedDevice(const clkCallback &cb)
{
    clocked_device_t cd = { cb, 0 };
    m_clocked_devices.push_back(cd);
}

// unregister a callback function which advances with the clock
void
system2200::unregisterClockedDevice(const clkCallback &/*cb*/) noexcept
{
#if 0
// FIXME: it is not possible to compare bound functions, so a different
// approach is needed
    for (auto it=begin(m_clocked_devices); it != end(m_clocked_devices); ++it) {
        if (it->callback_fn == cb) {
            m_clocked_devices.erase(it);
            break;
        }
    }
#else
    // if one board is unregistering, we are going to unregister everything.
    // big hammer, but it works.  FIXME find a nicer solution.
    m_clocked_devices.clear();
#endif
}

// build a system according to the spec.
// if a system already exists, tear it down and rebuild it.
void
system2200::setConfig(const SysCfgState &new_cfg)
{
    if (!current_cfg) {
        // first time we don't need to tear anything down
        current_cfg = std::make_shared<SysCfgState>();
    } else {
        // check if the change is minor, not requiring a teardown
        const bool rebuild_required = current_cfg->needsReboot(new_cfg);
        if (!rebuild_required) {
            *current_cfg = new_cfg;  // make new config permanent
            // notify all configured cards about possible new configuration
            for (int slot=0; slot < NUM_IOSLOTS; slot++) {
                if (current_cfg->isSlotOccupied(slot)) {
                    const IoCard::card_t ct = current_cfg->getSlotCardType(slot);
                    if (CardInfo::isCardConfigurable(ct)) {
                        auto cfg = current_cfg->getCardConfig(slot);
                        auto card = getInstFromSlot(slot);
                        card->setConfiguration(*cfg);
                    }
                }
            }
            return;
        }

        // the change was major, so delete existing resources
        cpu = nullptr;

        // remember which virtual disks are installed
        saveDiskMounts();

        // throw away all existing devices
        breakDownCards();
    }

    // save the new system configuration state
    *current_cfg = new_cfg;

    // (re)build the CPU
    const int ram_size = (current_cfg->getRamKB()) * 1024;
    int cpu_type = current_cfg->getCpuType();
    switch (cpu_type) {
        default:
            assert(false);
            cpu_type = Cpu2200::CPUTYPE_2200T;
            // fall through in non-debug build if config type is invalid
        case Cpu2200::CPUTYPE_2200B:
        case Cpu2200::CPUTYPE_2200T:
            cpu = std::make_shared<Cpu2200t>(scheduler, ram_size, cpu_type);
            break;
        case Cpu2200::CPUTYPE_VP:
        case Cpu2200::CPUTYPE_MVPC:
        case Cpu2200::CPUTYPE_MICROVP:
            cpu = std::make_shared<Cpu2200vp>(scheduler, ram_size, cpu_type);
            break;
    }
    assert(cpu);

    // build cards that go into each slot.
    // a hack -- when a display card is made, the crtframe status bar queries
    // to find out how many disk drives are associated with each drive.
    // if the display card is built before the disk controllers, the status
    // bar will be misconfigured.  the hack is to sweep the list twice,
    // skipping CRT's the first time, then doing only CRTs the second time.

    for (int pass=0; pass < 2; pass++) {
    for (int slot=0; slot < NUM_IOSLOTS; slot++) {

        if (!current_cfg->isSlotOccupied(slot)) {
            continue;
        }

        const IoCard::card_t cardtype = current_cfg->getSlotCardType(slot);
        const int io_addr             = current_cfg->getSlotCardAddr(slot) & 0xFF;

        const bool display = (cardtype == IoCard::card_t::disp_64x16)
                          || (cardtype == IoCard::card_t::disp_80x24)
                          || (cardtype == IoCard::card_t::term_mux) ;

        if ((pass == 0 && display) || (pass == 1 && !display)) {
            continue;
        }

        auto inst = IoCard::makeCard(scheduler, cpu, cardtype, io_addr,
                                     slot, current_cfg->getCardConfig(slot).get());
        if (inst == nullptr) {
            // failed to install
            UI_warn("Configuration problem: failure to create slot %d card instance", slot);
        } else {
            std::vector<int> addresses = inst->getAddresses();
            for (auto &addr : addresses) {
                ioMap[addr].slot = slot;
            }
            card_in_slot[slot] = std::move(inst);
        }
    }}

    restoreDiskMounts();    // remount disks
}

// give access to components
const SysCfgState&
system2200::config() noexcept
{
    return *current_cfg;
}

// the user requests a change in configuration from the UiFrontPanel.
// however, doing so often requires a tear down and rebuild of all the
// components.  destroying the frontpanel instance and then returning
// to it is uncouth.  instead, when the user wants to reconfigure, this
// function is called and that desire is noted, and we return immediately.
// later on, in the onIdle code, this flag is checked and the reconfiguration
// happens then.
void
system2200::reconfigure() noexcept
{
    m_do_reconfig = true;
}

// reset the cpu
void
system2200::reset(bool cold_reset)
{
    curIoAddr = -1;

    cpu->reset(cold_reset);

    // reset all I/O devices
    for (int slot=0; slot < NUM_IOSLOTS; slot++) {
        if (current_cfg->isSlotOccupied(slot)) {
            card_in_slot[slot]->reset(cold_reset);
        }
    }
}

// turn cpu speed regulation on (true) or off (false)
void
system2200::regulateCpuSpeed(bool regulated) noexcept
{
    current_cfg->regulateCpuSpeed(regulated);

    // reset the performance monitor history
    perf_hist_len = 0;
    perf_hist_ptr = 0;
}

// indicate if the CPU is throttled or not
bool
system2200::isCpuSpeedRegulated() noexcept
{
    return current_cfg->isCpuSpeedRegulated();
}

void
system2200::setDiskRealtime(bool realtime) noexcept
{
    current_cfg->setDiskRealtime(realtime);
}

// indicate if the disk emulation speed is throttled or not
bool
system2200::isDiskRealtime() noexcept
{
    return current_cfg->getDiskRealtime();
}

// halt emulation
void
system2200::freezeEmu(bool freeze) noexcept
{
    m_freeze_emu = freeze;
}

// called whenever there is free time
bool
system2200::onIdle()
{
    if (m_do_reconfig) {
        m_do_reconfig = false;
        freezeEmu(true);
        UI_systemConfigDlg();
        freezeEmu(false);
    }

    // this is something that needs to be tuned.  there is some unholy
    // interaction of this parameter with the windows scheduling due to
    // the fact that we call sleep(0) at the end of each slice.  small
    // changes in this value can have significant non-monotonic impact
    // on emulator performance.
    static const int slice_duration = 30;   // in ms

    switch (getTerminationState()) {
        case RUNNING:
            // this is the normal case during emulation
            if (m_freeze_emu) {
                // if we don't call sleep, we just get another onIdle event
                // and end up pegging the host CPU
                host::sleep(10);
            } else {
                emulateTimeslice(slice_duration);
            }
            return true;        // want more idle events
        case TERMINATING:
            // we've been signaled to shut down the universe.
            // change the flag to know we've already cleaned up, in case
            // we receive another onIdle call.
            setTerminationState(TERMINATED);
            cleanup();
            break;
        case TERMINATED:
            // do nothing -- we already requested a cleanup
            break;
        default:
            assert(false);
            break;
    }

    return false;        // don't want any more idle events
}

// simulate a few ms worth of instructions
void
system2200::emulateTimeslice(int ts_ms)
{
    const int num_devices = m_clocked_devices.size();

    // try to stae reatime within this window
    const int64 adj_window = 10LL*ts_ms;  // look at the last 10 timeslices

    if (cpu->status() != Cpu2200::CPU_RUNNING) {
        return;
    }

    const uint64 now_ms = host::getTimeMs();

    if (first_slice) {
        first_slice = false;
        realtime_start = now_ms;
    }
    const int64 realtime_elapsed = now_ms - realtime_start;
    int64 offset = adjust_sim_time - realtime_elapsed;

    if (offset > adj_window) {
        // we're way ahead (probably because we are running unregulated)
        adjust_sim_time = realtime_elapsed + adj_window;
        offset = adj_window;
    } else if (offset < -adj_window) {
        // we've fallen way behind; catch up so we don't
        // run like mad after any substantial pause
        adjust_sim_time = realtime_elapsed - adj_window;
        offset = -adj_window;
    }

    if ((offset > 0) && isCpuSpeedRegulated()) {

        // we are running ahead of schedule; kill some time.
        // we don't kill the full amount because the sleep function is
        // allowed to, and very well might, sleep longer than we asked.
        const unsigned int ioffset = static_cast<unsigned int>(offset & 0xFFFLL);  // bottom 4 sec or so
        host::sleep(ioffset/2);

    } else {

        // keep track of when each slice started
        perf_real_ms[perf_hist_ptr++] = now_ms;
        if (perf_hist_ptr >= perf_hist_size) {
            perf_hist_ptr -= perf_hist_size;
        }
        if (perf_hist_len < perf_hist_size) {
            perf_hist_len++;
        }

        // simulate one timeslice's worth of instructions
        int slice_ns = ts_ms*1000000;
        if (num_devices == 1) {

            auto cb = m_clocked_devices[0].callback_fn;
            while (slice_ns > 0) {
                const int op_ns = cb();
                if (op_ns > 10000) {
                    slice_ns = 0; // finish the timeslice
                } else  {
                    // the guard is to skip this if the device signals error
                    slice_ns -= op_ns;
                    scheduler->timerTick(op_ns);
                }
            }

        } else if (num_devices == 2) {
            // this is an important case of #0 being cpu, and #1 being
            // the 8080 in the MXD.  the cpu is faster than the 8080,
            // so let it run a few cycles between reevaluations of which
            // device is ahead in time.

            // at the start of a timeslice, shift time for all cards towards
            // zero to prevent overflowing the 32b nanosecond counters
            const uint32 rebase = std::min(m_clocked_devices[0].ns,
                                           m_clocked_devices[1].ns);
            m_clocked_devices[0].ns -= rebase;
            m_clocked_devices[1].ns -= rebase;

            // we try to keep the devices in time lockstep as much as we can.
            // each device has a nanosecond counter. the list of devices is
            // kept in sorted order of increasing time. we call entry 0, adjust
            // its time, then move it to the right place in the list.
            while (slice_ns > 0) {
                const bool run_vp = (   m_clocked_devices[0].ns
                                     <= m_clocked_devices[1].ns);
                int op_ns_signed = 0;
                uint32 op_ns = 0;
                if (run_vp) {
                    // the 2200vp executes about six instructions in the time
                    // the 8080 does one typical instruction
                    auto cb = m_clocked_devices[0].callback_fn;
                    op_ns_signed  = cb();
                    op_ns_signed += cb();
                    op_ns_signed += cb();
                    op_ns_signed += cb();
                    op_ns_signed += cb();
                    op_ns_signed += cb();
                    op_ns = static_cast<uint32>(op_ns_signed);
                    m_clocked_devices[0].ns += op_ns;
                } else {
                    auto cb = m_clocked_devices[1].callback_fn;
                    op_ns_signed = cb();
                    op_ns = static_cast<uint32>(op_ns_signed);
                    m_clocked_devices[1].ns += op_ns;
                }

                if (op_ns > 50000) {
                    // something went wrong; finish the timeslice
                    slice_ns = 0;
                } else {
                    const uint32 clamp_ns =
                        std::max(m_clocked_devices[1].ns, m_clocked_devices[0].ns)
                      - std::min(m_clocked_devices[1].ns, m_clocked_devices[0].ns);
                    const uint32 delta_ns = std::min(op_ns, clamp_ns);
                    slice_ns -= delta_ns;
                    scheduler->timerTick(delta_ns);
                }
            }

        } else {

            // there are multiple devices

            // moving m_clocked_devices entries around it expensive,
            // so this array is used to maintain the order of the devices
            // where [0] is the one most behind in time.
            std::vector<int> order;
            for (int n=0; n < num_devices; n++) {
                order.push_back(n);
            }
            // bubble sort in time order
            for (int n=0; n < num_devices; n++) {
                for (int k=0; k < num_devices-1; k++) {
                    if (m_clocked_devices[order[k]].ns >
                        m_clocked_devices[order[k+1]].ns) {
                        std::swap(order[k], order[k+1]);
                    }
                }
            }
            // double check they are sorted in time order
            for (int k=0; k < num_devices-1; k++) {
                assert (m_clocked_devices[order[k]].ns <=
                        m_clocked_devices[order[k+1]].ns);
            }

            // at the start of a timeslice, shift time for all cards towards
            // zero to prevent overflowing the 32b nanosecond counters
            const uint32 rebase = m_clocked_devices[order[0]].ns;
            for (auto idx : order) {
                assert(m_clocked_devices[idx].ns >= rebase);
                m_clocked_devices[idx].ns -= rebase;
            }

            // we try to keep the devices in time lockstep as much as we can.
            // each device has a nanosecond counter. the list of devices is
            // kept in sorted order of increasing time. we call entry 0, adjust
            // its time, then move it to the right place in the list.
            while (slice_ns > 0) {
                auto cb = m_clocked_devices[order[0]].callback_fn;
                const int op_ns_signed = cb();
                const uint32 op_ns = static_cast<uint32>(op_ns_signed);
                if (op_ns > 50000) {
                    slice_ns = 0; // finish the timeslice
                } else {
                    // we can't advance world time by op_ns if the next most
                    // oldest device would conceptually start before time
                    // gets to where device[0] ended up after op_ns.
                    // TODO: investigate a more efficient way to do all
                    // of this vs min() and then later sorting the clocked devices.
                    // at the very least, special case having two devices,
                    // rather than all this generalized sorting for N devices.
                    const uint32 clamp_ns = m_clocked_devices[order[1]].ns
                                          - m_clocked_devices[order[0]].ns;
                    const uint32 delta_ns = std::min(op_ns, clamp_ns);
                    slice_ns -= delta_ns;
                    scheduler->timerTick(delta_ns);
                    const uint32 new_ns = (m_clocked_devices[order[0]].ns += op_ns);
                    auto entry0 = order[0];
                    int i;
                    for (i=0; i < num_devices-1; ++i) {
                        if (m_clocked_devices[order[i+1]].ns < new_ns) {
                            order[i] = order[i+1];
                        } else {
                            break;
                        }
                    }
                    order[i] = entry0;
                }
            }
        } // 3 or more clocked devices

        sim_time_ns     += ts_ms;
        adjust_sim_time += ts_ms;

        if (cpu->status() != Cpu2200::CPU_RUNNING) {
            UI_warn("CPU halted -- must reset");
            cpu->reset(true);  // hard reset
            return;
        }

        sim_seconds = static_cast<unsigned long>(
                            (sim_time_ns/1000) & 0xFFFFFFFF);

        const int real_seconds_now = static_cast<int>(realtime_elapsed/1000);
        if (real_seconds != real_seconds_now) {
            real_seconds = real_seconds_now;
            if (perf_hist_len > 10) {
                // compute running performance average over the
                // last real second or so
                const int n1 = (perf_hist_ptr - 1 + perf_hist_size)
                             % perf_hist_size;
                int64 ms_diff = 0;
                int slices = 0;
                for (int n=1; n < perf_hist_len; n+=10) {
                    const int n0 = (n1 - n + perf_hist_size) % perf_hist_size;
                    slices = n;
                    ms_diff = (perf_real_ms[n1] - perf_real_ms[n0]);
                    if (ms_diff > 1000) {
                        break;
                    }
                }
                const float relative_speed = static_cast<float>(slices*ts_ms)
                                           / static_cast<float>(ms_diff);

                // update the status bar with simulated seconds and performance
                UI_setSimSeconds(sim_seconds, relative_speed);
            }
        }

        // at least yield so we don't hog the whole machine
        host::sleep(0);
    }
}

// ========================================================================
//    io dispatch functions (used by core sim)
// ========================================================================

// address byte strobe
void
system2200::dispatchAbsStrobe(uint8 byte)
{
    // done if reselecting same device
    if (byte == curIoAddr) {
        return;
    }

    // deselect old card
    if ((curIoAddr > 0) && (ioMap[curIoAddr].slot >= 0)) {
        (card_in_slot[ioMap[curIoAddr].slot])->deselect();
    }
    curIoAddr = byte;

    const int cpu_type = cpu->getCpuType();
    const bool vp_mode = (cpu_type != Cpu2200::CPUTYPE_2200B)
                      && (cpu_type != Cpu2200::CPUTYPE_2200T);

    // by default, assume the device is not ready.
    // the addressed card will turn it back below if appropriate
    if (curIoAddr == 0x00 && vp_mode) {
        // the (M)VP CPU special cases address 00 and forces ready true
        cpu->setDevRdy(true);
        return;
    }
    // nobody is driving, so it defaults to 0
    cpu->setDevRdy(false);

    // let the selected card know it has been chosen
    if (ioMap[curIoAddr].slot >= 0) {
        const int slot = ioMap[curIoAddr].slot;
        card_in_slot[slot]->select();
        return;
    }

    // MVP OS probes addr 80 to test for the bank select register (BSR).
    // for non-VSLI CPUs, it would be annoying to get warned about it.
    if (vp_mode && (curIoAddr == 0x80)) {
        return;
    }

    // MVP allows extra RAM to be used as a RAM disk at /340
    if (vp_mode && (curIoAddr == 0x40)) {
        return;
    }

    // warn the user that a non-existent device has been selected
    if (!ioMap[curIoAddr].ignore && current_cfg->getWarnIo()
        && (curIoAddr != 0x00)  // intentionally select nothing
        && (curIoAddr != 0x46)  // testing for mxd at 0x4n
        && (curIoAddr != 0x86)  // testing for mxd at 0x8n
        && (curIoAddr != 0xC6)  // testing for mxd at 0xCn
       ) {
        const bool response = UI_confirm(
                    "Warning: selected non-existent I/O device %02X\n"
                    "Should I warn you of further accesses to this device?",
                    curIoAddr);
        // suppress further warnings
        ioMap[curIoAddr].ignore = !response;
    }
}

// output byte strobe
void
system2200::dispatchObsStrobe(uint8 byte)
{
// p 6-2 of the Wang 2200 Service Manual:
// When the controller is selected (select latch set), the Ready/Busy
// Decoder will indicate Ready (active low) to the CPU only if the peripheral
// device is not doing an operation.  The Ready indicator will stay active
// until the peripheral device being used generates a Busy indicator,
// allowing the CPU to do another I/O operation.  Normally, the device
// being used will generate a Busy indicator after the I/O Bus (!OB1 - !OB8)
// has been strobed by !OBS, the CPU output strobe.
    if (curIoAddr > 0) {
        if (ioMap[curIoAddr].slot >= 0) {
            card_in_slot[ioMap[curIoAddr].slot]->strobeOBS(byte);
        }
    }
}

// handles CBS strobes
void
system2200::dispatchCbsStrobe(uint8 byte)
{
    // each card handles CBS in its own way.
    //   * many cards simply ignore it
    //   * some use it like another OBS strobe to capture some type
    //     of command word
    //   * some cards use it to trigger an IBS strobe
    if ((curIoAddr > 0) && (ioMap[curIoAddr].slot >= 0)) {
        card_in_slot[ioMap[curIoAddr].slot]->strobeCBS(byte);
    }
}

// notify selected card when CPB changes
void
system2200::dispatchCpuBusy(bool busy)
{
    if ((curIoAddr > 0) && (ioMap[curIoAddr].slot >= 0)) {
        // signal that we want to get something
        card_in_slot[ioMap[curIoAddr].slot]->setCpuBusy(busy);
    }
}

// the CPU can poll IB5 without any other strobe.  return that bit.
int
system2200::cpuPollIB()
{
    if  ((curIoAddr > 0) && (ioMap[curIoAddr].slot >= 0)) {
        // signal that we want to get something
        return card_in_slot[ioMap[curIoAddr].slot]->getIB();
    }
    return 0;
}

// ========================================================================
// keyboard input routing
// ========================================================================

// register a handler for a key event to a given keyboard terminal
void
system2200::registerKb(int io_addr, int term_num, const kbCallback &cb)
{
    // check that it isn't already registered
    for (auto &kb : keyboard_routes) {
        if (io_addr == kb.io_addr && term_num == kb.term_num) {
            UI_warn("Attempt to register kb handler at io_addr=0x%02x, term_num=%d twice",
                    io_addr, term_num);
            return;
        }
    }
    kb_route_t kb = { io_addr, term_num, cb, nullptr };
    keyboard_routes.push_back(kb);
}

void
system2200::unregisterKb(int io_addr, int term_num)
{
    for (auto it = begin(keyboard_routes); it != end(keyboard_routes); ++it) {
        if (io_addr == it->io_addr && term_num == it->term_num) {
            keyboard_routes.erase(it);
            return;
        }
    }
    UI_warn("Attempt to unregister unknown kb handler at io_addr=0x%02x, term_num=%d",
            io_addr, term_num);
}

// send a key event to the specified keyboard/terminal
void
system2200::dispatchKeystroke(int io_addr, int term_num, int keyvalue)
{
    for (auto &kb : keyboard_routes) {
        if (io_addr == kb.io_addr && term_num == kb.term_num) {
            if (kb.script_handle) {
                // a script is running; ignore everything but HALT
                // (on pc, ctrl-C or pause/break key)
                if (keyvalue == IoCardKeyboard::KEYCODE_HALT) {
                    kb.script_handle = nullptr;
                }
                return;
            }
            auto cb = kb.callback_fn;
            cb(keyvalue);
            return;
        }
    }
    UI_warn("Attempt to route key to unknown kb handler at io_addr=0x%02x, term_num=%d",
            io_addr, term_num);
}

// request the contents of a file to be fed in as a keyboard stream
void
system2200::invokeKbScript(int io_addr, int term_num,
                           const std::string &filename)
{
    if (isScriptModeActive(io_addr, term_num)) {
        UI_warn("Attempt to invoke a script while one already active, io_addr=0x%02x, term_num=%d",
                io_addr, term_num);
        return;
    }

    for (auto &kb : keyboard_routes) {
        if (io_addr == kb.io_addr && term_num == kb.term_num) {
            const int flags = ScriptFile::SCRIPT_META_INC
                            | ScriptFile::SCRIPT_META_HEX
                            | ScriptFile::SCRIPT_META_KEY ;
            kb.script_handle = std::make_shared<ScriptFile>(
                                   filename, flags, 3 /*max nesting*/
                               );
            if (!kb.script_handle->openedOk()) {
                kb.script_handle = nullptr;
            } else {
                // possibly get the first character
                pollScriptInput(io_addr, term_num);
            }
            return;
        }
    }
    UI_warn("Attempt to invoke script on unknown kb handler at io_addr=0x%02x, term_num=%d",
            io_addr, term_num);
}

// indicates if a script is currently active on a given terminal
bool
system2200::isScriptModeActive(int io_addr, int term_num)
{
    for (auto &kb : keyboard_routes) {
        if (io_addr == kb.io_addr && term_num == kb.term_num) {
            return (kb.script_handle != nullptr);
        }
    }
    UI_warn("Attempt to query script status on unknown kb handler at io_addr=0x%02x, term_num=%d",
            io_addr, term_num);
    return false;
}

// return how many terminals at this io_addr have active scripts
int
system2200::numActiveScripts(int io_addr) noexcept
{
    int count = 0;
    for (auto &kb : keyboard_routes) {
        if (io_addr == kb.io_addr) {
            count++;
        }
    }
    return count;
}

// when invoked on a terminal in script mode, causes key callback to be
// invoked with the next character from the script.  it returns true if
// a script supplied a character.
bool
system2200::pollScriptInput(int io_addr, int term_num)
{
    for (auto &kb : keyboard_routes) {
        if (io_addr == kb.io_addr && term_num == kb.term_num) {
            if (!kb.script_handle) {
                return false;
            }
            int ch;
            if (kb.script_handle->getNextByte(&ch)) {
                // yes, a key is available
                auto cb = kb.callback_fn;
                cb(ch);
                return true;
            }
            // EOF
            kb.script_handle = nullptr;
            return false;
        }
    }
    return false;
}

// ========================================================================
// external interface to the slot manager
// ========================================================================

// returns false if the slot is empty, otherwise true.
// returns card type index and io address via pointers.
bool
system2200::getSlotInfo(int slot, int *cardtype_idx, int *addr) noexcept
{
    assert(0 <= slot && slot < NUM_IOSLOTS);
    if (!current_cfg->isSlotOccupied(slot)) {
        return false;
    }

    if (cardtype_idx != nullptr) {
        *cardtype_idx = static_cast<int>(current_cfg->getSlotCardType(slot));
    }

    if (addr != nullptr) {
        *addr = current_cfg->getSlotCardAddr(slot);
    }

    return true;
}

// returns the IO address of the n-th keyboard (0-based).
// if (n >= # of keyboards), returns -1.
int
system2200::getKbIoAddr(int n) noexcept
{
    int num = 0;

    for (int slot=0; slot < NUM_IOSLOTS; slot++) {
        if (current_cfg->getSlotCardType(slot) == IoCard::card_t::keyboard) {
            if (num == n) {
                return current_cfg->getSlotCardAddr(slot);
            }
            num++;
        }
    }

    return -1; // failed to find it
}

// returns the IO address of the n-th printer (0-based).
// if (n >= # of printers), returns -1.
int
system2200::getPrinterIoAddr(int n) noexcept
{
    int num = 0;

    for (int slot=0; slot < NUM_IOSLOTS; slot++) {
        if (current_cfg->getSlotCardType(slot) == IoCard::card_t::printer) {
            if (num == n) {
                return current_cfg->getSlotCardAddr(slot);
            }
            num++;
        }
    }

    return -1;  // failed to find it
}

// return the instance handle of the device at the specified IO address.
// consumers of this function don't own the card, so a raw pointer is used.
IoCard*
system2200::getInstFromIoAddr(int io_addr) noexcept
{
    assert((io_addr >= 0) && (io_addr <= 0xFFF));
    return card_in_slot[ioMap[io_addr & 0xFF].slot].get();
}

// given a slot, return the "this" element
// consumers of this function don't own the card, so a raw pointer is used.
IoCard*
system2200::getInstFromSlot(int slot) noexcept
{
    assert(slot >=0 && slot < NUM_IOSLOTS);
    return card_in_slot[slot].get();
}

// find slot number of disk controller #n.
// returns true if successful.
bool
system2200::findDiskController(const int n, int *slot) noexcept
{
    const int num_ioslots = NUM_IOSLOTS;
    int numfound = 0;
    assert(slot != nullptr);

    assert(n >= 0);

    for (int probe=0; probe < num_ioslots; probe++) {
        if (isDiskController(probe)) {
            if (numfound++ == n) {
                *slot = probe;
                return true;
            }
        }
    } // for probe

    return false;
}

// find slot,drive of any disk controller with disk matching name.
// returns true if successful.
// note: slot, drive, io_addr are optionally nullptr.
bool
system2200::findDisk(const std::string &filename,
                     int *slot, int *drive, int *io_addr)
{
    for (int controller=0; ; controller++) {

        int slt = 0;
        if (!findDiskController(controller, &slt)) {
            break;
        }

        const auto cfg = current_cfg->getCardConfig(slt);
        const auto dcfg = dynamic_cast<const DiskCtrlCfgState*>(cfg.get());
        assert(dcfg);
        const int num_drives = dcfg->getNumDrives();
        for (int d=0; d < num_drives; d++) {
            const int stat = IoCardDisk::wvdDriveStatus(slt, d);
            if ((stat & IoCardDisk::WVD_STAT_DRIVE_EXISTENT) != 0  &&
                (stat & IoCardDisk::WVD_STAT_DRIVE_OCCUPIED) != 0) {
                std::string fname;
                bool ok = IoCardDisk::wvdGetFilename(slt, d, &fname);
                assert(ok);
                if (filename == fname) {
                    if (slot != nullptr) {
                        *slot = slt;
                    }
                    if (drive != nullptr) {
                        *drive = d;
                    }
                    if (io_addr != nullptr) {
                        ok = getSlotInfo(slt, nullptr, io_addr);
                        assert(ok);
                    }
                    return true;
                }
            }
        } // for (d)
    } // for (controller)

    return false;
}

// ------------------------------------------------------------------------
// legal cpu system configurations
// ------------------------------------------------------------------------

const std::vector<system2200::cpuconfig_t> system2200::m_cpu_configs = {
    // https://wang2200.org/docs/datasheet/2200ABC_CPU_DataSheet.700-3491.11-74.pdf
    {   Cpu2200::CPUTYPE_2200B,    // .cpu_type
        "2200B",                   // .label
        { 4, 8, 12, 16, 24, 32 },  // .ram_size_options (20K and 28K were conceptually options too)
        { 0 },                     // .ucode_size_options
        false                      // .has_oneshot
    },

    // https://wang2200.org/docs/datasheet/2200T_CPU_DataSheet.700-3723D.1-79.pdf
    {   Cpu2200::CPUTYPE_2200T,    // .cpu_type
        "2200T",                   // .label
        { 8, 16, 24, 32 },         // .ram_size_options
        { 0 },                     // .ucode_size_options
        false                      // .has_oneshot
    },

    // https://wang2200.org/docs/datasheet/2200VP_CPU_DataSheet.700-4051C.10-80.pdf
    {   Cpu2200::CPUTYPE_VP,       // .cpu_type
        "2200VP",                  // .label
        { 16, 32, 48, 64 },        // .ram_size_options
        { 32 },                    // .ucode_size_options
        false                      // .has_oneshot
    },

#if 0 // remove it to reduce cognitive overload
    // https://wang2200.org/docs/datasheet/2200MVP_DataSheet.700-4656G.10-82.pdf
    {   Cpu2200::CPUTYPE_MVP,      // .cpu_type
        "2200MVP",                 // .label
        { 32, 64, 128, 256 },      // .ram_size_options
        { 32 },                    // .ucode_size_options
        true                       // .has_oneshot
    },
#endif

    // https://wang2200.org/docs/datasheet/MVP_LVP_SystemsOptionC.700-6832.4-81.pdf
    // https://wang2200.org/docs/system/2200SystemsOptionC.729-1062.pdf
    {   Cpu2200::CPUTYPE_MVPC,     // .cpu_type
        "2200MVP-C",               // .label
        { 32, 64, 128, 256, 512 }, // .ram_size_options
        { 64 },                    // .ucode_size_options
        true                       // .has_oneshot
    },

    // https://wang2200.org/docs/system/2200MicroVP_MaintenanceManual.741-1668.5-88.pdf
    {   Cpu2200::CPUTYPE_MICROVP,  // .cpu_type
        "MicroVP",                 // .label
        { 128, 512, 1024, 2048, 4096, 8192 },  // .ram_size_options
        { 64 },                    // .ucode_size_options
        true                       // .has_oneshot
    },
};

const system2200::cpuconfig_t*
system2200::getCpuConfig(const std::string &config_name) noexcept
{
    for (auto const &cfg : system2200::m_cpu_configs) {
        if (cfg.label == config_name) {
            return &cfg;
        }
    }
    return nullptr;
}

const system2200::cpuconfig_t*
system2200::getCpuConfig(int config_id) noexcept
{
    for (auto const &cfg : system2200::m_cpu_configs) {
        if (cfg.cpu_type == config_id) {
            return &cfg;
        }
    }
    return nullptr;
}

// vim: ts=8:et:sw=4:smarttab


text:
;
BEGIN;

;
ALTER TABLE reading ADD COLUMN groupid integer NOT NULL;

;

COMMIT;

redpajama_set_name: RedPajamaGithub


text:
Examples
========

Crawler
-------

A cooperative i/o library wouldn't be such without a "crawler" example:

.. literalinclude:: ../examples/crawler.py

Echo server
-----------

.. literalinclude:: ../examples/echo-server.py


text:
'use strict';

// Declare app level module which depends on filters, and services
angular.module('carrental', [
  'ngRoute',
  'ui.bootstrap.datetimepicker',
  'carrental.filters',
  'carrental.services',
  'carrental.directives',
  'carrental.controllers'
]).
config(['$routeProvider', '$httpProvider', function($routeProvider, $httpProvider) {
  $routeProvider.when('/search', {templateUrl: 'partials/search.html', controller: 'SearchCtrl'});
  $routeProvider.when('/reservations', {templateUrl: 'partials/reservation-list.html', controller: 'ReservationListCtrl'});
  $routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'LoginCtrl'});
  $routeProvider.when('/settings', {templateUrl: 'partials/settings.html', controller: 'SettingsCtrl'});
  $routeProvider.otherwise({redirectTo: '/search'});
  $httpProvider.defaults.useXDomain = true;
  $httpProvider.defaults.withCredentials = true;
}]).
run(['$rootScope',  '$location',  function ($rootScope,  $location) {
  $rootScope.loggedIn = false;

  $rootScope.$on( "$routeChangeStart", function(event, next, current) {
    if ( $rootScope.user == null ) {
      if ( next.templateUrl == "partials/login.html" ) {
        // already going to #login, no redirect needed
      } else {
        // not going to #login, we should redirect now
        $location.path( "/login" );
      }
    }
  });
  $rootScope.logout = function() {
    $rootScope.loggedIn = false;
    $rootScope.user = undefined;
    $location.path('/login').replace();
  }
}]);


text:
#include "tkInt.h"

/*
 * The following are a list of the predefined atom strings. They should match
 * those found in xatom.h
 */

static const char *const atomNameArray[] = {
    "PRIMARY",		"SECONDARY",		"ARC",
    "ATOM",		"BITMAP",		"CARDINAL",
    "COLORMAP",		"CURSOR",		"CUT_BUFFER0",
    "CUT_BUFFER1",	"CUT_BUFFER2",		"CUT_BUFFER3",
    "CUT_BUFFER4",	"CUT_BUFFER5",		"CUT_BUFFER6",
    "CUT_BUFFER7",	"DRAWABLE",		"FONT",
    "INTEGER",		"PIXMAP",		"POINT",
    "RECTANGLE",	"RESOURCE_MANAGER",	"RGB_COLOR_MAP",
    "RGB_BEST_MAP",	"RGB_BLUE_MAP",		"RGB_DEFAULT_MAP",
    "RGB_GRAY_MAP",	"RGB_GREEN_MAP",	"RGB_RED_MAP",
    "STRING",		"VISUALID",		"WINDOW",
    "WM_COMMAND",	"WM_HINTS",		"WM_CLIENT_MACHINE",
    "WM_ICON_NAME",	"WM_ICON_SIZE",		"WM_NAME",
    "WM_NORMAL_HINTS",	"WM_SIZE_HINTS",	"WM_ZOOM_HINTS",
    "MIN_SPACE",	"NORM_SPACE",		"MAX_SPACE",
    "END_SPACE",	"SUPERSCRIPT_X",	"SUPERSCRIPT_Y",
    "SUBSCRIPT_X",	"SUBSCRIPT_Y",		"UNDERLINE_POSITION",
    "UNDERLINE_THICKNESS", "STRIKEOUT_ASCENT",	"STRIKEOUT_DESCENT",
    "ITALIC_ANGLE",	"X_HEIGHT",		"QUAD_WIDTH",
    "WEIGHT",		"POINT_SIZE",		"RESOLUTION",
    "COPYRIGHT",	"NOTICE",		"FONT_NAME",
    "FAMILY_NAME",	"FULL_NAME",		"CAP_HEIGHT",
    "WM_CLASS",		"WM_TRANSIENT_FOR",
    NULL
};

/*
 * Forward references to functions defined in this file:
 */

static void	AtomInit(TkDisplay *dispPtr);

/*
 *--------------------------------------------------------------
 *
 * Tk_InternAtom --
 *
 *	Given a string, produce the equivalent X atom. This function is
 *	equivalent to XInternAtom, except that it keeps a local cache of
 *	atoms. Once a name is known, the server need not be contacted again
 *	for that name.
 *
 * Results:
 *	The return value is the Atom corresponding to name.
 *
 * Side effects:
 *	A new entry may be added to the local atom cache.
 *
 *--------------------------------------------------------------
 */

Atom
Tk_InternAtom(
    Tk_Window tkwin,		/* Window token; map name to atom for this
				 * window's display. */
    const char *name)		/* Name to turn into atom. */
{
    TkDisplay *dispPtr;
    Tcl_HashEntry *hPtr;
    int isNew;

    dispPtr = ((TkWindow *) tkwin)->dispPtr;
    if (!dispPtr->atomInit) {
	AtomInit(dispPtr);
    }

    hPtr = Tcl_CreateHashEntry(&dispPtr->nameTable, name, &isNew);
    if (isNew) {
	Tcl_HashEntry *hPtr2;
	Atom atom;

	atom = XInternAtom(dispPtr->display, name, False);
	Tcl_SetHashValue(hPtr, INT2PTR(atom));
	hPtr2 = Tcl_CreateHashEntry(&dispPtr->atomTable, INT2PTR(atom), &isNew);
	Tcl_SetHashValue(hPtr2, Tcl_GetHashKey(&dispPtr->nameTable, hPtr));
    }
    return (Atom)PTR2INT(Tcl_GetHashValue(hPtr));
}

/*
 *--------------------------------------------------------------
 *
 * Tk_GetAtomName --
 *
 *	This function is equivalent to XGetAtomName except that it uses the
 *	local atom cache to avoid contacting the server.
 *
 * Results:
 *	The return value is a character string corresponding to the atom given
 *	by "atom". This string's storage space is static: it need not be freed
 *	by the caller, and should not be modified by the caller. If "atom"
 *	doesn't exist on tkwin's display, then the string "?bad atom?"  is
 *	returned.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

const char *
Tk_GetAtomName(
    Tk_Window tkwin,		/* Window token; map atom to name relative to
				 * this window's display. */
    Atom atom)			/* Atom whose name is wanted. */
{
    TkDisplay *dispPtr;
    Tcl_HashEntry *hPtr;

    dispPtr = ((TkWindow *) tkwin)->dispPtr;
    if (!dispPtr->atomInit) {
	AtomInit(dispPtr);
    }

    hPtr = Tcl_FindHashEntry(&dispPtr->atomTable, INT2PTR(atom));
    if (hPtr == NULL) {
	const char *name;
	Tk_ErrorHandler handler;
	int isNew;
	char *mustFree = NULL;

	handler = Tk_CreateErrorHandler(dispPtr->display, BadAtom, -1, -1,
		NULL, NULL);
	name = mustFree = XGetAtomName(dispPtr->display, atom);
	if (name == NULL) {
	    name = "?bad atom?";
	}
	Tk_DeleteErrorHandler(handler);
	hPtr = Tcl_CreateHashEntry(&dispPtr->nameTable, name, &isNew);
	Tcl_SetHashValue(hPtr, INT2PTR(atom));
	if (mustFree) {
	    XFree(mustFree);
	}
	name = Tcl_GetHashKey(&dispPtr->nameTable, hPtr);
	hPtr = Tcl_CreateHashEntry(&dispPtr->atomTable, INT2PTR(atom), &isNew);
	Tcl_SetHashValue(hPtr, name);
    }
    return Tcl_GetHashValue(hPtr);
}

/*
 *--------------------------------------------------------------
 *
 * AtomInit --
 *
 *	Initialize atom-related information for a display.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Tables get initialized, etc. etc..
 *
 *--------------------------------------------------------------
 */

static void
AtomInit(
    TkDisplay *dispPtr)/* Display to initialize. */
{
    Tcl_HashEntry *hPtr;
    Atom atom;

    dispPtr->atomInit = 1;
    Tcl_InitHashTable(&dispPtr->nameTable, TCL_STRING_KEYS);
    Tcl_InitHashTable(&dispPtr->atomTable, TCL_ONE_WORD_KEYS);

    for (atom = 1; atom <= XA_LAST_PREDEFINED; atom++) {
	const char *name;
	int isNew;

	hPtr = Tcl_FindHashEntry(&dispPtr->atomTable, INT2PTR(atom));
	if (hPtr != NULL) {
	    continue;
	}

	name = atomNameArray[atom - 1];
	hPtr = Tcl_CreateHashEntry(&dispPtr->nameTable, name, &isNew);
	Tcl_SetHashValue(hPtr, INT2PTR(atom));
	name = Tcl_GetHashKey(&dispPtr->nameTable, hPtr);
	hPtr = Tcl_CreateHashEntry(&dispPtr->atomTable, INT2PTR(atom), &isNew);
	Tcl_SetHashValue(hPtr, name);
    }
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */


text:
---
layout: post
title: 源码分析从AIDL看Binder进程间通信的流程
categories: Android
description: Binder通信是Android系统架构的基础。本文尝试从AIDL的使用开始理解系统的Binder通信。
keywords: 源码阅读,AIDL,Binder
---

`Binder`通信是Android系统架构的基础。本文尝试从AIDL的使用开始理解系统的`Binder`通信。

### 0x00 一个AIDL的例子

首先我们创建一个项目,写一个`RemoteService.java`,并定义个`AIDL`接口`IRemoteService.aidl`

```java
interface IRemoteService {
    String getText();
}
```

这时候IDE会自动在目录`build/generated/source/aidl/debug/`生成`IRemoteService.java`文件。

本文为了方便调试和理解`AIDL`的过程,我们把生成的`IRemoteService.java`文件拷贝出来,放在`app/main/java`目录下,然后把`aidl`文件夹删除。

`RemoteService`为服务端,`MainActivity`为客户端。最后项目结构为

![最终项目结构](../images/binder-auto-code.png)

### 0x01 远程服务RemoteService

```java
public class RemoteService extends Service {
    public final static String ACTION = "net.angrycode.RemoteService";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    /**
     * 定义远程服务对外接口
     */
    IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        @Override
        public String getText() throws RemoteException {
            return "text from remote,pid:" + Process.myPid();
        }
    };
}
```

在`RemoteService`中定义`IBinder`接口,并在`onBind()`方法中返回,供客户端使用。

最后在`mainifest`文件中注册远程服务,指定进程为私有进程

```java
<service android:name=".RemoteService"
    android:process=":remote">
    <intent-filter>
        <action android:name="net.angrycode.RemoteService"/>
    </intent-filter>
</service>
```

### 0x02 本地客户端MainActivity

```java
public class MainActivity extends AppCompatActivity {
    private TextView mTextMessage;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextMessage = (TextView) findViewById(R.id.message);
    }

    public void onClickBind(View view) {
        Intent service = new Intent(this, RemoteService.class);
        service.setAction(RemoteService.ACTION);
        bindService(service, conn, Context.BIND_AUTO_CREATE);
    }
    public void onClickUnBind(View view) {
        unbindService(conn);
    }
    ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IRemoteService iRemoteService = IRemoteService.Stub.asInterface(service);
            try {//连接之后获取到远程服务text
                String text = iRemoteService.getText();
                mTextMessage.setText(text);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Toast.makeText(getApplication(), "远程服务已连接", Toast.LENGTH_LONG).show();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Toast.makeText(getApplication(), "远程服务已断开", Toast.LENGTH_LONG).show();
        }
    };
}
```

本地客户端实现了`ServiceConnection`接口,用于监听远程服务的连接状态,并在`onServiceConnected()`中拿到远程服务`RemoteService`对外的接口`IRemoteService`的引用。

当客户端进行绑定远程服务时,就使用`IRemoteService.Stub.asInterface(IBinder)`获取到远程服务对象,客户端与服务端的通信就开始了。

![运行结果](../images/show-demo.png)

### 0x03 IRemoteService接口

系统自动生成的这个文件中有除了我们定义`getText()`方法外还生成了两个内部类`Stub`和`Proxy`。

```java
public interface IRemoteService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends Binder implements IRemoteService {
        private static final java.lang.String DESCRIPTOR = "net.angrycode.learnpro.IRemoteService";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an net.angrycode.learnpro.IRemoteService interface,
         * generating a proxy if needed.
         */
        public static IRemoteService asInterface(IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof IRemoteService))) {
                return ((IRemoteService) iin);
            }
            return new IRemoteService.Stub.Proxy(obj);
        }

        @Override
        public IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getText: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _result = this.getText();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements IRemoteService {
            private IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.lang.String getText() throws RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getText, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_getText = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public java.lang.String getText() throws android.os.RemoteException;
}
```

`Stub`类继承于`Binder`,但它们都实现了`IRemoteService`接口。

`Binder`是何物呢?

> Base class for a remotable object, the core part of a lightweight remote procedure call mechanism defined by Binder.This class is an implementation of IBinder that provides standard local implementation of such an object.

可以看出`Binder`是一个远程对象,它实现了提供本地标准接口的`IBinder`。

![Stub-Proxy](../images/stub-proxy.png)

**`Stub`类代表着远程服务,而`Proxy`代表着远程服务在本地的代理。**

### 0x04 获取Binder对象

在客户端`MainActivity`中,绑定远程服务之后,使用`IRemoteService.Stub.asInterface()`方法获取到远程服务的`Binder`对象。

```java
/**
 * Cast an IBinder object into an net.angrycode.learnpro.IRemoteService interface,
 * generating a proxy if needed.
 */
public static net.angrycode.learnpro.IRemoteService asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof net.angrycode.learnpro.IRemoteService))) {
        return ((net.angrycode.learnpro.IRemoteService) iin);
    }
    return new net.angrycode.learnpro.IRemoteService.Stub.Proxy(obj);
}
```

这个方法先查找本地是否存在这个对象,存在则返回;不存在则返回一个`Proxy`对象。

通过定点调试,可以知道当`RemoteService`在子进程中时,`asInterface(obj)`参数是一个`BinderProxy`对象,这个是远程服务进程的代理类。这个时候返回给客户端的是`Proxy`对象。

![binder-asinterface](../images/binder-asinterface.png)

**客户端与服务端不在同一进程时,通过`BinderProxy`进行通信**。

当把`manifest`中`RemoteService`的`android:process=':remote'`配置去掉时,`asInterface(obj)`的参数的传递就是`RemoteService$1`,其实就是`RemoteService`里面的内部类`Stub`。

![同进程中的IBinder对象就是RemoteService](../images/binder-remoteservice.png)

然后我们再回到多进程的流程来,跳转到`Proxy`中

### 0x05 Proxy.transact()

通过名字知道`Proxy`就是远程服务的代理,它持有`Binder`的引用。当客户端调用`iRemoteService.getText()`时其实是进入到`Proxy`类中`getText()`方法。

```java
public java.lang.String getText() throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.lang.String _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        mRemote.transact(Stub.TRANSACTION_getText, _data, _reply, 0);
        _reply.readException();
        _result = _reply.readString();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}
```
首先获取到两个`Parcel`对象,这个是进程间通信的数据结构。`_data`和`_reply`分别为`getText()`需要传递的参数和返回值,`getText()`无需参数,只有`String`类型返回值。

然后调用`mRemote`的`transact()`方法(其实就是调用`BinderProxy`的`transact()`方法)。然后通过`_reply`获取到执行方法后的返回值,这里就是一个`RemoteService`里面实现的`String`。

**在`Proxy`中执行`transact()`方法后又回调到哪里了呢?**

在`onTransact()`方法中设置一个断点,通过调试,我们发现其实是**回调到了`Stub`类中`onTransact()`方法**。

### 0x06 Stub.onTransact()

```java
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getText: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _result = this.getText();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
```

`onTransact()`方法中第一个参数`code`是与`transact()`第一个参数`code`是对应的,这是客户端与服务端约定好的常量。

这时候会执行到`onTransact()`方法中的`_result = this.getText()`方法。而`Stub`类是在`RemoteService`中实现的,故就访问到远程服务中资源了。

### 0x07 总结

通过以上流程分析可以知道,通过`bindService`绑定一个服务之后在`onServiceConnected()`中拿到了远程服务的在本地的`Proxy`,通过它与远程服务进行通信。

![Binder在App中的流程](../images/binder-flow.png)


text:
import path from 'path';
import os from 'os';
import { assert, test } from 'yeoman-generator';

describe('sails-rest-api:blueprints', function () {
  this.timeout(10000);

  before(done => {
    test
      .run(path.join(__dirname, '../../generators/blueprints'))
      .inDir(path.join(os.tmpdir(), './temp-test'))
      .on('end', done);
  });

  it('Should properly create root files', () => {
    assert.file([
      'api/blueprints/add.js',
      'api/blueprints/create.js',
      'api/blueprints/destroy.js',
      'api/blueprints/find.js',
      'api/blueprints/findone.js',
      'api/blueprints/populate.js',
      'api/blueprints/remove.js',
      'api/blueprints/update.js',
      'test/unit/blueprints/add.test.js',
      'test/unit/blueprints/create.test.js',
      'test/unit/blueprints/destroy.test.js',
      'test/unit/blueprints/find.test.js',
      'test/unit/blueprints/findone.test.js',
      'test/unit/blueprints/populate.test.js',
      'test/unit/blueprints/remove.test.js',
      'test/unit/blueprints/update.test.js'
    ]);
  });
});


text:
using UnityEngine;
using UnityEngine.EventSystems;

namespace ClumsyBat.InputManagement
{
    public class TouchInput
    {
        public enum TouchAction
        {
            None,
            LeftTap,
            RightTap,
            SwipeLeft,
            SwipeRight
        }

        private const float SwipeResistanceX = 70f;
        private const float SwipeResistanceY = 90f;
        private const float StationaryTime = 0.05f; // Time before a tap is registered
        private const float SwipeTime = 0.4f;       // Player must swipe within this time

        private bool isGestureSet;
        private float touchStartPos;
        private float touchStartTime;

        public bool IsJumpHeld { get; private set; }

        private PlayerInputHandler inputHandler;

        public TouchInput(PlayerInputHandler inputHandler)
        {
            this.inputHandler = inputHandler;
        }

        public TouchAction GetAction()
        {
            if (Input.touches.Length == 0) return TouchAction.None;
            Touch touch = Input.touches[0];

            if (EventSystem.current.IsPointerOverGameObject(touch.fingerId)) return TouchAction.None;

            if (touch.phase == TouchPhase.Ended)
            {
                IsJumpHeld = false;
            }

            if (touch.phase == TouchPhase.Began)
            {
                isGestureSet = false;
                touchStartPos = touch.position.x;
                touchStartTime = Time.time;
                IsJumpHeld = true;
                return TouchAction.None;
            }
            else if (isGestureSet)
            {
                return TouchAction.None;
            }

            switch (touch.phase)
            {
                case TouchPhase.Stationary:
                    return ResolveStationaryTouch(touch.position.x);
                case TouchPhase.Moved:
                    return ResolveMovedTouch(touch.position.x);
                case TouchPhase.Ended:
                    return ResolveEndedTouch(touch.position.x);
            }
            return TouchAction.None;
        }

        private TouchAction ResolveStationaryTouch(float touchPosX)
        {
            if (IsWithinStationaryTime())
            {
                return TouchAction.None;
            }
            else
            {
                isGestureSet = true;
                if (touchPosX < 0.5 * Screen.width)
                {
                    return TouchAction.LeftTap;
                }
                else
                {
                    return TouchAction.RightTap;
                }
            }
        }

        private TouchAction ResolveMovedTouch(float touchPosX)
        {
            if (IsWithinSwipeTime() && RightSwipeDistanceAchieved(touchPosX))
            {
                isGestureSet = true;
                return TouchAction.SwipeRight;
            }
            else if (IsWithinSwipeTime() && LeftSwipeDistanceAchieved(touchPosX))
            {
                isGestureSet = true;
                return TouchAction.SwipeLeft;
            }
            else
            {
                return ResolveStationaryTouch(touchPosX);
            }
        }

        private TouchAction ResolveEndedTouch(float touchPosX)
        {
            return ResolveMovedTouch(touchPosX);
        }

        private bool IsWithinSwipeTime()
        {
            return Time.time < touchStartTime + SwipeTime;
        }

        private bool IsWithinStationaryTime()
        {
            return Time.time < touchStartTime + StationaryTime;
        }

        private bool LeftSwipeDistanceAchieved(float touchPosX)
        {
            return touchPosX - touchStartPos < -SwipeResistanceX;
        }

        private bool RightSwipeDistanceAchieved(float touchPosX)
        {
            return touchPosX - touchStartPos > SwipeResistanceX;
        }
    }
}


text:
package chapter_10;

/**
 * Question 10.3
 */
public class SearchInRotatedArray {
    public int search(int[] nums, int target) {
        if (nums.length == 0) return -1;
        return search(nums, target, 0, nums.length - 1);
    }

    private int search(int[] nums, int target, int start, int end) {
        if (start >= end) return (nums[start] == target) ? start : -1;
        int middle = (start + end) / 2;
        if (nums[middle] == target) return middle;
        else if (nums[middle] > nums[end]) {
            return (target >= nums[start] && middle > 0 && target <= nums[middle - 1])
                    ? search(nums, target, start, middle - 1) : search(nums, target, middle + 1, end);
        } else {
            return (target >= nums[middle + 1] && middle < nums.length - 1 && target <= nums[end])
                    ? search(nums, target, middle + 1, end) : search(nums, target, start, middle - 1);
        }
    }
}


text:
angular.module('curates.singleCollection', [])

.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider
  .state('singleCollection', {
    url: '/:url',
    templateUrl: 'modules/singleCollection/singleCollection.html'
  })
})

.controller('singleCollectionController', function($scope, $stateParams, collectionFactory, userManagement) {
  var url = $stateParams.url;
  collectionFactory.getCollection(url).then(function(collection) {
    if (collection != null) {
      $scope.isUser =
        (userManagement.user.id === collection.user.id &&
        userManagement.user.provider === collection.user.provider);
      $scope.collection = collection;
    }
  });
});


text:
{{<layout}}

{{$pageTitle}}Childcare and early years{{/pageTitle}}

{{$content}}

Home

Education

Childcare and early years

Childcare and early years

Early years settings

Early years foundation stage (EYFS)

Special educational needs and disabilities (SEND)

Training for early years staff

Inspections

Early years pupil premium

Childcare costs

Funding

Early years settings

Childminders

Nurseries

Children's centres

Top services

Find a childminder

Set up a nursery

Find a children's centre

Become an early years teacher

Top publications

Show all publications

Boost for childminders and hardworking families

Childminder agencies and changes to the local authority role

Childminder agency trial evaluation

Childminder choice has never been better thanks to first agency

{{/content}}

{{/layout}}


text:
package com.justwayward.reader.ui.activity;

import android.content.Context;
import android.content.Intent;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;

import com.justwayward.reader.R;
import com.justwayward.reader.base.BaseActivity;
import com.justwayward.reader.bean.BookListTags;
import com.justwayward.reader.bean.support.TagEvent;
import com.justwayward.reader.common.OnRvItemClickListener;
import com.justwayward.reader.component.AppComponent;
import com.justwayward.reader.component.DaggerFindComponent;
import com.justwayward.reader.ui.adapter.SubjectTagsAdapter;
import com.justwayward.reader.ui.contract.SubjectBookListContract;
import com.justwayward.reader.ui.fragment.SubjectFragment;
import com.justwayward.reader.ui.presenter.SubjectBookListPresenter;
import com.justwayward.reader.utils.ToastUtils;
import com.justwayward.reader.view.RVPIndicator;
import com.justwayward.reader.view.ReboundScrollView;
import com.justwayward.reader.view.SupportDividerItemDecoration;

import org.greenrobot.eventbus.EventBus;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.inject.Inject;

import butterknife.Bind;

public class SubjectBookListActivity extends BaseActivity implements SubjectBookListContract.View, OnRvItemClickListener<String> {

    @Bind(R.id.indicatorSubject)
    RVPIndicator mIndicator;
    @Bind(R.id.viewpagerSubject)
    ViewPager mViewPager;
    @Bind(R.id.rsvTags)
    ReboundScrollView rsvTags;

    @Bind(R.id.rvTags)
    RecyclerView rvTags;
    private SubjectTagsAdapter mTagAdapter;
    private List<BookListTags.DataBean> mTagList = new ArrayList<>();

    private List<Fragment> mTabContents;
    private FragmentPagerAdapter mAdapter;
    private List<String> mDatas;

    @Inject
    SubjectBookListPresenter mPresenter;

    private String currentTag = "";

    public static void startActivity(Context context) {
        Intent intent = new Intent(context, SubjectBookListActivity.class);
        context.startActivity(intent);
    }

    @Override
    public int getLayoutId() {
        return R.layout.activity_subject_book_list_tag;
    }

    @Override
    protected void setupActivityComponent(AppComponent appComponent) {
        DaggerFindComponent.builder()
                .appComponent(appComponent)
                .build()
                .inject(this);
    }

    @Override
    public void initToolBar() {
        mCommonToolbar.setTitle(R.string.subject_book_list);
        mCommonToolbar.setNavigationIcon(R.drawable.ab_back);
    }

    @Override
    public void initDatas() {
        mDatas = Arrays.asList(getResources().getStringArray(R.array.subject_tabs));

        mTabContents = new ArrayList<>();
        mTabContents.add(SubjectFragment.newInstance("", 0));
        mTabContents.add(SubjectFragment.newInstance("", 1));
        mTabContents.add(SubjectFragment.newInstance("", 2));

        mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public int getCount() {
                return mTabContents.size();
            }

            @Override
            public Fragment getItem(int position) {
                return mTabContents.get(position);
            }
        };
    }

    @Override
    public void configViews() {
        mIndicator.setTabItemTitles(mDatas);
        mViewPager.setAdapter(mAdapter);
        mIndicator.setViewPager(mViewPager, 0);

        rvTags.setHasFixedSize(true);
        rvTags.setLayoutManager(new LinearLayoutManager(this));
        rvTags.addItemDecoration(new SupportDividerItemDecoration(this, LinearLayoutManager.VERTICAL));
        mTagAdapter = new SubjectTagsAdapter(this, mTagList);
        mTagAdapter.setItemClickListener(this);
        rvTags.setAdapter(mTagAdapter);

        mPresenter.attachView(this);
        mPresenter.getBookListTags();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_subject, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.menu_tags) {
            if (isVisible(rsvTags)) {
                hideTagGroup();
            } else {
                showTagGroup();
            }
            return true;
        } else if (item.getItemId() == R.id.menu_my_book_list) {
            startActivity(new Intent(this, MyBookListActivity.class));
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        if (isVisible(rsvTags)) {
            hideTagGroup();
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public void showBookListTags(BookListTags data) {
        mTagList.clear();
        mTagList.addAll(data.data);
        mTagAdapter.notifyDataSetChanged();
    }

    @Override
    public void showError() {

    }

    @Override
    public void complete() {
    }

    @Override
    public void onItemClick(View view, int position, String data) {
        hideTagGroup();
        currentTag = data;
        EventBus.getDefault().post(new TagEvent(currentTag));
    }

    private void showTagGroup() {
        if (mTagList.isEmpty()) {
            ToastUtils.showToast(getString(R.string.network_error_tips));
            return;
        }
        Animation mShowAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
                Animation.RELATIVE_TO_SELF, 0.0f,
                Animation.RELATIVE_TO_SELF, -1.0f,
                Animation.RELATIVE_TO_SELF, 0.0f);
        mShowAction.setDuration(400);
        rsvTags.startAnimation(mShowAction);
        rsvTags.setVisibility(View.VISIBLE);
    }

    private void hideTagGroup() {
        Animation mHiddenAction = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
                Animation.RELATIVE_TO_SELF, 0.0f,
                Animation.RELATIVE_TO_SELF, 0.0f,
                Animation.RELATIVE_TO_SELF, -1.0f);
        mHiddenAction.setDuration(400);
        rsvTags.startAnimation(mHiddenAction);
        rsvTags.setVisibility(View.GONE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.detachView();
        }
    }
}


text:
package javax.swing.plaf;

/**
 * An abstract base class for delegates that implement the pluggable
 * look and feel for a JSlider.
 *
 * @see javax.swing.JSlider
 *
 * @author Andrew Selkirk (aselkirk@sympatico.ca)
 * @author Sascha Brawer (brawer@dandelis.ch)
 */
public abstract class SliderUI extends ComponentUI
{
  /**
   * Constructs a new SliderUI.
   */
  public SliderUI()
  {
    // Nothing to do here.
  }
}


text:
- You should create a gem using JRuby that wraps an existing Java library.
- Your gem should work with a Java library that doesn't already have
  a good wrapper.
- You should make the API for your library look and feel like Ruby, not Java.

## Wrapping cue.language

For this assignment, I'm wrapping the
[cue.language](https://github.com/vcl/cue.language) library, which
handles "counting strings, identifying languages, and removing stop
words."

## How to Use It

### It's All In the TextComb Module

There are methods for splitting up text:

```ruby
require 'text_comb'

string = "He must be a little nuts. He is. I mean he just isn't well
          screwed on is he?"

p TextComb.words(string).to_a

# prints:
["He", "must", "be", "a", "little", "nuts", "He", "is", "I", "mean"...

TextComb.sentences(string).each do |sentence|
  p sentence
end

# prints:
"He must be a little nuts. "
"He is. "
"I mean he just isn't well screwed on is he?"

TextComb.ngrams(string, 5).each do |ngram|
  p ngram
end

# prints:
"He must be a little"
"must be a little nuts"
"I mean he just isn't"
...
```

TextComb can take a guess at the language, and use the appropriate stop-words:

```ruby
string = "That's a great mustache, grandma!"
TextComb.ngrams(string, 2, :stop_words=>:guess).to_a

# returns:
["great mustache", "mustache grandma"]
```

If it picks wrong, and you know what you're dealing with, you can specify:

```ruby
TextComb.ngrams(string, 3, :stop_words=>:Croatian).each do |ngram|
  puts ngram
end
```

If you're curious, TextComb will tell you how it guessed:

```ruby
string = "J'ai la moutarde dans ma moustache."
TextComb.guess_language(string).to_s   # "French"
```

### Mix-in TextComb::StringExtensions

Extend a string with TextComb::StringExtensions, and it'll have those
methods:

```ruby
require 'text_comb'

motto = "I came. I saw. I hacked."
motto.extend(TextComb::StringExtensions)

motto.sentences.to_a    # ["I came. ", "I saw. ", "I hacked."]
motto.words.to_a.uniq   # ["I", "came", "saw", "hacked"]
```

Stop-words for n-grams work the same way, too.

```ruby
string = "I saw red roosters at Ted's farm."
string.extend(TextComb::StringExtensions)
string.ngrams(2, :stop_words => :English).to_a

# returns:
["saw red", "red roosters", "Ted's farm"]
```

### Make a TextComb::String

TextComb::String includes TextComb::StringExtensions, but delegates everything
else to its string. It's like mixing TextComb::StringExtensions into your
own string.

```ruby
require 'text_comb'

littany = TextComb::String.new("I must not fear.")
littany.ngrams(3).to_a   # ["I must not", "must not fear"]
```

Even handier, there's the TextComb.string method to save you some
finger-tapping.

```ruby
require 'text_comb'
littany = TextComb.string("I must not fear.")
littany.words.to_a  # -> ["I", "must", "not", "fear."]

littany.guess_language.to_s  # -> :English
```

### Future Plans

- code(TextComb.ngrams) currently yields whole strings - maybe split
  them into Arrays of words.


text:
@echo off

REM Switch for Release/Debug build
REM cl -nologo -EHsc /Oi /Od /Zi /FC filerandomizer.cpp /link /INCREMENTAL:NO /PDB:filerandomizer.pdb -opt:ref user32.lib ole32.lib shell32.lib Gdi32.lib
cl -nologo -EHsc /O2 filerandomizer.cpp /link -opt:ref user32.lib ole32.lib shell32.lib Gdi32.lib

del filerandomizer.obj


text:
package net.adrianlehmann.swt_revision.patterns.variation_patterns.templatemethod;

/**
 * Created by adrianlehmann on 09.07.17.
 */
public class Main {
    public static void main(String[] args) {
        new MateTea().prepare();
        //Adding leaves
        //Boiling
        //Giving to poor sleepless student

        new Coffee().prepare();
        //Crushing beans
        //Boiling
        //Giving to poor sleepless student
    }
}


text:
Functions will be CamelCase to match Psychtoolbox.
Variables will be under_scored, with an underscore separating words.

## File Organization

The abstract class for experiments, as well as all classes implementing experiments, can be found
in the `matlab/experiments` directory.


text:
<?php

declare(strict_types=1);

include __DIR__ . '/basics.php';

use React\EventLoop\Factory;
use unreal4u\TelegramAPI\HttpClientRequestHandler;
use unreal4u\TelegramAPI\Telegram\Methods\GetWebhookInfo;
use unreal4u\TelegramAPI\Telegram\Types\WebhookInfo;
use unreal4u\TelegramAPI\TgLog;

$loop = Factory::create();
$tgLog = new TgLog(BOT_TOKEN, new HttpClientRequestHandler($loop));

$webHookInfo = new GetWebhookInfo();
$promise = $tgLog->performApiRequest($webHookInfo);

$promise->then(
    function (WebhookInfo $info) {
        echo '<pre>';
        var_dump($info);
        echo '</pre>';
    },
    function (\Exception $e) {
        var_dump($e);
    }
);
$loop->run();


text:
"use strict";

var Vector = require("./math/vector");
var integrators = require("./math/integrators");

var PhysicsManager = function (friction) {
    this.integrator = new integrators.Euler();
    this.fixedStep = 1 / 60;
    this.friction = friction || 5; // wood
    this.collisions = [];
};

var deadObjectFilter = function (collection) {
    return !collection.isDead;
};

PhysicsManager.prototype.update = function (objects) {
    this.integrator.integrate(objects.filter(deadObjectFilter),
                              this.fixedStep,
                              this.friction);
};

function Pair(first, second) {
    this.first = first;
    this.second = second;
}

PhysicsManager.prototype.getCollisionPairs = function (group1, group2) {
    var filteredGroup1 = group1.filter(deadObjectFilter),
        filteredGroup2 = group2.filter(deadObjectFilter);

    this.collisions = [];
    for (var i = 0; i < filteredGroup1.length; i++) {
        var first = filteredGroup1[i];
        var firstCenter = first.getCenter();

        for (var j = 0; j < filteredGroup2.length; j++) {
            var second = filteredGroup2[j];

            var secondCenter = second.getCenter();
            var distance = firstCenter.subtract(secondCenter).length();
            var radiusSum = first.collisionRadius + second.collisionRadius;

            if (distance < radiusSum) {
                this.collisions.push(new Pair(first, second));
            }
        }
    }
    return this.collisions;
};

PhysicsManager.prototype.getObjectsWithinRadius = function (objects,
                                                            point,
                                                            radius) {

    if (radius === 0)
        return [];

    var neighbours = [];
    for (var i = 0; i < objects.length; i++) {
        var obj = objects[i];

        var center = obj.getCenter();
        var distance = center.subtract(point).length();
        var radiusSum = obj.collisionRadius + radius;
        if (distance < radiusSum)
            neighbours.push(obj);
    }
    return neighbours;
};

PhysicsManager.prototype.applyForce = function (object, force) {
    Vector.add(object.acceleration,
               force.divide(object.mass),
               object.acceleration);
};

PhysicsManager.prototype.circleIntersects = function (center1, radius1,
                                                      center2, radius2) {

    var distance = center1.subtract(center2).length();
    var radiusSum = radius1 + radius2;
    return distance <= radiusSum;
};

module.exports = PhysicsManager;


text:
<?php

use yii\helpers\Html;
use yii\widgets\ActiveForm;

/* @var $this yii\web\View */
/* @var $model app\modules\models\WarehouseInfo */
/* @var $form yii\widgets\ActiveForm */
?>

    <?php $form = ActiveForm::begin(); ?>

    <?= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>

    <?= $form->field($model, 'desc')->textarea(['rows' => 6]) ?>

    <?= $form->field($model, 'address')->textInput(['maxlength' => true]) ?>

    <?= $form->field($model, 'contact')->textInput(['maxlength' => true]) ?>

    <?= $form->field($model, 'phone')->textInput(['maxlength' => true]) ?>

    <?= $form->field($model, 'area')->textInput() ?>

    <?= $form->field($model, 'price')->textInput(['maxlength' => true]) ?>

    <?= $form->field($model, 'create_time')->textInput() ?>

    <?= $form->field($model, 'update_time')->textInput() ?>

    <?= $form->field($model, 'status')->textInput() ?>

        <?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>

    <?php ActiveForm::end(); ?>


text:
<?php
namespace Pulsestorm\Magento2\Generate\Ui\Addformfield;
use function Pulsestorm\Pestle\Importer\pestle_import;
pestle_import('Pulsestorm\Pestle\Library\output');
pestle_import('Pulsestorm\Pestle\Library\exitWithErrorMessage');
pestle_import('Pulsestorm\Pestle\Library\writeStringToFile');
pestle_import('Pulsestorm\Magento2\Cli\Library\addSpecificChild');
pestle_import('Pulsestorm\Xml_Library\formatXmlString');

function validateXml($xml, $argv)
{
    if($xml->getName() !== 'form')
    {
        exitWithErrorMessage('ERROR: This does not look like a <form/> file.');
    }

    $fieldsets = $xml->xpath('/form/fieldset[@name="'.$argv['fieldset'].'"]');
    if(count($fieldsets) !== 1)
    {
        exitWithErrorMessage('ERROR: XML file should have exactly one name="'.$argv['fieldset'].'" fieldset.');
    }

    $fields = $xml->xpath('/form/fieldset/field[@name="'.$argv['field'].'"]');
    if(count($fields) > 0)
    {
        exitWithErrorMessage("ERROR: XML file already has a name=\"{$argv['field']}\" field.");
    }
}

function getNextSortOrderFromXml($xml)
{
    $nodes = $xml->xpath('//item[@name="sortOrder"]');
    $max   = array_reduce($nodes, function($carry, $item){
        $item = (int) $item;
        if($carry > $item)
        {
            return $carry;
        }
        return $item;
    }, 0);

    return $max + 10;
}

/**
* Adds a Form Field
*
* @command magento2:generate:ui:add-form-field
* @argument path_xml Path to Form XML File?
* @argument field Field Name? [title]
* @argument label Label? [Title]
* @argument fieldset Fieldset Name? [general]
* @option is-required Is field required?
*/
function pestle_cli($argv, $options)
{
    $xml = simplexml_load_file($argv['path_xml']);
    validateXml($xml, $argv);
    $fieldsets  = $xml->xpath('/form/fieldset[@name="'.$argv['fieldset'].'"]');
    $fieldset   = array_shift($fieldsets);

    $dataType = 'text';
    $formElement = 'input';
    $sortOrder   = '25';

    $field      = $fieldset->addChild('field');
    $field->addAttribute('name', $argv['field']);
    // addSpecificChild('field', $fieldset,);
    $argument           = addSpecificChild('argument', $field, 'data', 'array');
    $itemConfig         = addSpecificChild('item', $argument, 'config', 'array');
    $itemDataType       = addSpecificChild('item', $itemConfig, 'dataType', 'string', $dataType);
    $itemLabel          = addSpecificChild('item', $itemConfig, 'label', 'string', $argv['label']);
    $itemFormElement    = addSpecificChild('item', $itemConfig, 'formElement', 'string', $formElement);
    $itemSortOrder      = addSpecificChild('item', $itemConfig, 'sortOrder', 'string', getNextSortOrderFromXml($fieldset));
    $itemDataScope      = addSpecificChild('item', $itemConfig, 'dataScope', 'string', $argv['field']);

    $itemValidation     = addSpecificChild('item', $itemConfig, 'validation', 'array');
    $required           = is_null($options['is-required']) ? 'false' : 'true';
    $itemRequiredEntry  = addSpecificChild('item', $itemValidation, 'required-entry', 'boolean', $required);

    writeStringToFile(
        $argv['path_xml'],
        formatXmlString($xml->asXml())
    );
}


text:
set -e

function start_rgw {
  get_config
  check_config

  if [ "${CEPH_GET_ADMIN_KEY}" -eq 1 ]; then
    get_admin_key
    check_admin_key
  fi

  # Check to see if our RGW has been initialized
  if [ ! -e "$RGW_KEYRING" ]; then

    if [ ! -e "$RGW_BOOTSTRAP_KEYRING" ]; then
      log "ERROR- $RGW_BOOTSTRAP_KEYRING must exist. You can extract it from your current monitor by running 'ceph auth get client.bootstrap-rgw -o $RGW_BOOTSTRAP_KEYRING'"
      exit 1
    fi

    ceph_health client.bootstrap-rgw "$RGW_BOOTSTRAP_KEYRING"

    # Generate the RGW key
    ceph "${CLI_OPTS[@]}" --name client.bootstrap-rgw --keyring "$RGW_BOOTSTRAP_KEYRING" auth get-or-create client.rgw."${RGW_NAME}" osd 'allow rwx' mon 'allow rw' -o "$RGW_KEYRING"
    chown "${CHOWN_OPT[@]}" ceph. "$RGW_KEYRING"
    chmod 0600 "$RGW_KEYRING"
  fi

  log "SUCCESS"

  exec /usr/bin/radosgw "${DAEMON_OPTS[@]}" -n client.rgw."${RGW_NAME}" -k "$RGW_KEYRING"
}

function create_rgw_user {

  # Check to see if our RGW has been initialized
  if [ ! -e "$RGW_KEYRING" ]; then
    log "ERROR- $RGW_KEYRING must exist. Please get it from your Rados Gateway"
    exit 1
  fi

  local user_key=""
  if [ -n "${RGW_USER_SECRET_KEY}" ]; then
    user_key="--access-key=${RGW_USER_USER_KEY} --secret=${RGW_USER_SECRET_KEY}"
  fi

  exec radosgw-admin user create --uid="${RGW_USER}" "${user_key}" --display-name="RGW ${RGW_USER} User" -c /etc/ceph/"${CLUSTER}".conf
}


text:
using System;
using System.Collections.Generic;

namespace charcolle.Utility.CustomAssetImporter {

    // TreeElementUtility and TreeElement are useful helper classes for backend tree data structures.
    // See tests at the bottom for examples of how to use.
    internal static class TreeElementUtility {

        internal static void TreeToList<T>( T root, IList<T> result ) where T : TreeElement {
            if( result == null )
                throw new NullReferenceException( "The input 'IList<T> result' list is null" );
            result.Clear();

            Stack<T> stack = new Stack<T>();
            stack.Push( root );

            while( stack.Count > 0 ) {
                T current = stack.Pop();
                result.Add( current );

                if( current.children != null && current.children.Count > 0 ) {
                    for( int i = current.children.Count - 1; i >= 0; i-- ) {
                        stack.Push( ( T )current.children[ i ] );
                    }
                }
            }
        }

        // Returns the root of the tree parsed from the list (always the first element).
        // Important: the first item and is required to have a depth value of -1.
        // The rest of the items should have depth >= 0.
        public static T ListToTree<T>( IList<T> list ) where T : TreeElement {
            // Validate input
            ValidateDepthValues( list );

            // Clear old states
            foreach( var element in list ) {
                element.parent = null;
                element.children = null;
            }

            // Set child and parent references using depth info
            for( int parentIndex = 0; parentIndex < list.Count; parentIndex++ ) {
                var parent = list[ parentIndex ];
                bool alreadyHasValidChildren = parent.children != null;
                if( alreadyHasValidChildren )
                    continue;

                int parentDepth = parent.depth;
                int childCount = 0;

                // Count children based depth value, we are looking at children until it's the same depth as this object
                for( int i = parentIndex + 1; i < list.Count; i++ ) {
                    if( list[ i ].depth == parentDepth + 1 )
                        childCount++;
                    if( list[ i ].depth <= parentDepth )
                        break;
                }

                // Fill child array
                List<TreeElement> childList = null;
                if( childCount != 0 ) {
                    childList = new List<TreeElement>( childCount ); // Allocate once
                    childCount = 0;
                    for( int i = parentIndex + 1; i < list.Count; i++ ) {
                        if( list[ i ].depth == parentDepth + 1 ) {
                            list[ i ].parent = parent;
                            childList.Add( list[ i ] );
                            childCount++;
                        }

                        if( list[ i ].depth <= parentDepth )
                            break;
                    }
                }

                parent.children = childList;
            }

            return list[ 0 ];
        }

        // Check state of input list
        public static void ValidateDepthValues<T>( IList<T> list ) where T : TreeElement {
            if( list.Count == 0 )
                throw new ArgumentException( "list should have items, count is 0, check before calling ValidateDepthValues", "list" );

            if( list[ 0 ].depth != -1 )
                throw new ArgumentException( "list item at index 0 should have a depth of -1 (since this should be the hidden root of the tree). Depth is: " + list[ 0 ].depth, "list" );

            for( int i = 0; i < list.Count - 1; i++ ) {
                int depth = list[ i ].depth;
                int nextDepth = list[ i + 1 ].depth;
                if( nextDepth > depth && nextDepth - depth > 1 )
                    throw new ArgumentException( string.Format( "Invalid depth info in input list. Depth cannot increase more than 1 per row. Index {0} has depth {1} while index {2} has depth {3}", i, depth, i + 1, nextDepth ) );
            }

            for( int i = 1; i < list.Count; ++i )
                if( list[ i ].depth < 0 )
                    throw new ArgumentException( "Invalid depth value for item at index " + i + ". Only the first item (the root) should have depth below 0." );

            if( list.Count > 1 && list[ 1 ].depth != 0 )
                throw new ArgumentException( "Input list item at index 1 is assumed to have a depth of 0", "list" );
        }

        // For updating depth values below any given element e.g after reparenting elements
        public static void UpdateDepthValues<T>( T root ) where T : TreeElement {
            if( root == null )
                throw new ArgumentNullException( "root", "The root is null" );

            if( !root.hasChildren )
                return;

            Stack<TreeElement> stack = new Stack<TreeElement>();
            stack.Push( root );
            while( stack.Count > 0 ) {
                TreeElement current = stack.Pop();
                if( current.children != null ) {
                    foreach( var child in current.children ) {
                        child.depth = current.depth + 1;
                        stack.Push( child );
                    }
                }
            }
        }

        // Returns true if there is an ancestor of child in the elements list
        private static bool IsChildOf<T>( T child, IList<T> elements ) where T : TreeElement {
            while( child != null ) {
                child = ( T )child.parent;
                if( elements.Contains( child ) )
                    return true;
            }
            return false;
        }

        public static IList<T> FindCommonAncestorsWithinList<T>( IList<T> elements ) where T : TreeElement {
            if( elements.Count == 1 )
                return new List<T>( elements );

            List<T> result = new List<T>( elements );
            result.RemoveAll( g => IsChildOf( g, elements ) );
            return result;
        }
    }

}


text:
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
		 xmlns="http://maven.apache.org/POM/4.0.0"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-stream-parent</artifactId>
		<version>1.0.0.BUILD-SNAPSHOT</version>
	</parent>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-stream-test-support</artifactId>
	<name>Spring Cloud Stream Test Support</name>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-stream-binder-spi</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-autoconfigure</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-stream</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>


text:
import { BTCTxProvider } from '../btc';

export class LTCTxProvider extends BTCTxProvider {
  lib = require('bitcore-lib-ltc');
  create({ recipients, utxos = [], change, wallet, feeRate, fee = 20000 }) {
    change = change || wallet.deriveAddress(wallet.addressIndex, true);
    const filteredUtxos = this.selectCoins(recipients, utxos, fee);
    const btcUtxos = filteredUtxos.map(utxo => {
      const btcUtxo = Object.assign({}, utxo, {
        amount: utxo.value / 1e8,
        txid: utxo.mintTxid,
        outputIndex: utxo.mintIndex
      });
      return new this.lib.Transaction.UnspentOutput(btcUtxo);
    });
    let tx = new this.lib.Transaction().from(btcUtxos);
    if (fee) {
      tx.fee(fee);
    }
    if (feeRate) {
      tx.feePerKb(Number(feeRate) * 1000); // feeRate is in feePerByte
    }
    if (change) {
      tx.change(change);
    }
    for (const recipient of recipients) {
      tx.to(recipient.address, parseInt(recipient.amount));
    }
    return tx.uncheckedSerialize();
  }
}


text: module.exports = require('./pub/src/index.js');