All Downloads are FREE. Search and download functionalities are using the official Maven repository.

functionalj.environments.Console Maven / Gradle / Ivy

// ============================================================================
// Copyright (c) 2017-2019 Nawapunth Manusitthipol (NawaMan - http://nawaman.net).
// ----------------------------------------------------------------------------
// MIT License
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ============================================================================
package functionalj.environments;

import static functionalj.function.Func.f;
import static functionalj.ref.Run.With;
import static java.lang.String.format;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import functionalj.InterruptedRuntimeException;
import functionalj.function.FuncUnit1;
import functionalj.functions.ThrowFuncs;
import functionalj.list.FuncList;
import functionalj.promise.DeferAction;
import functionalj.promise.Promise;
import functionalj.ref.ComputeBody;
import functionalj.ref.RunBody;
import functionalj.stream.BlockingQueueIteratorPlus;
import functionalj.stream.StreamPlus;
import lombok.val;

public final class Console {
    
    private Console() {
    }
    
    public static FuncUnit1 print   = Console::print;
    public static FuncUnit1 println = Console::println;
    public static FuncUnit1 printf(String format) {
        return obj -> print(format(format, obj));
    }
    
    public static Console.Instance print(Object text) {
        return Env.console().print(text);
    }
    public static Console.Instance println(Object line) {
        return Env.console().println(line);
    }
    public static Console.Instance println() {
        return Env.console().println();
    }
    
    
    public static Console.Instance outPrint(Object text) {
        return Env.console().outPrint(text);
    }
    public static Console.Instance outPrintln(Object line) {
        return Env.console().outPrintln(line);
    }
    public static Console.Instance outPrintln() {
        return Env.console().outPrintln();
    }
    
    
    public static Console.Instance errPrint(Object text) {
        return Env.console().errPrint(text);
    }
    public static Console.Instance errPrintln(Object line) {
        return Env.console().errPrintln(line);
    }
    public static Console.Instance errPrintln() {
        return Env.console().outPrintln();
    }
    
    
    public static String readln() {
        return Env.console().readln();
    }
    public static String pollln() {
        return Env.console().pollln();
    }
    public static Promise inputLine() {
        return Env.console().inputLine();
    }
    
    public static void stopRead() {
        Env.console().stopRead();
    }
    
    
    public static abstract class Instance {
        
        public final Console.Instance print(Object text) {
            return outPrint(text);
        }
        public final Console.Instance println(Object line) {
            return outPrintln(line);
        }
        public final Console.Instance printf(String format, Object ... args) {
            return outPrintf(format, args);
        }
        public final Console.Instance println() {
            return outPrintln();
        }
        
        
        public abstract Console.Instance outPrint(Object text);
        
        public abstract Console.Instance outPrintln(Object line);
        
        public abstract Console.Instance outPrintf(String format, Object ... args);
        
        public abstract Console.Instance outPrintln();
        
        
        public abstract Console.Instance errPrint(Object text);
        
        public abstract Console.Instance errPrintln(Object line);
        
        public abstract Console.Instance errPrintf(String format, Object ... args);
        
        public abstract Console.Instance errPrintln();
        
        
        public abstract String readln();
        public abstract String pollln();
        
        public Promise inputLine() {
            return DeferAction.run(()->{
                String str;
                while ((str = pollln()) == null) {
                    Thread.sleep(1);
                }
                return str;
            })
            .getPromise();
        }
        
        public abstract void   stopRead();
        
    }
    
    public static class StubRecord {
        
        private final DATA             data;
        private final FuncList outLines;
        private final FuncList errLines;
        private final FuncList inLines;
        
        public StubRecord(DATA data, FuncList outLines, FuncList errLines, FuncList inLines) {
            this.data     = data;
            this.outLines = outLines;
            this.errLines = errLines;
            this.inLines  = inLines;
        }
        
        public DATA getData() {
            return data;
        }
        
        public StreamPlus outLines() {
            return outLines.stream();
        }
        public StreamPlus errLines() {
            return errLines.stream();
        }
        public StreamPlus inLines() {
            return inLines.stream();
        }
        
        public String toString() {
            return "++++++++++++++++++++\n" +
                   "Data: " + data + "\n" + 
                   "outLines(" + outLines.size() + "): \n    " + outLines.joinToString("\n    ") + "\n" +
                   "errLines(" + errLines.size() + "): \n    " + errLines.joinToString("\n    ") + "\n" +
                   "inLines("  + inLines.size()  + "): \n    "  + inLines.joinToString("\n    ")  + "\n" +
                   "--------------------"
                   ;
        }
        
    }
    
    public static  StubRecord useStub(RunBody body) throws E {
        return useStub(new ConsoleInQueue(), ()->{ body.run(); return null; });
    }
    
    public static  StubRecord useStub(ComputeBody body) throws E {
        return useStub(new ConsoleInQueue(), ()->{ body.run(); return null; });
    }
    
    public static  StubRecord useStub(FuncUnit1 holder, RunBody body) throws E {
        val inQueue = new ConsoleInQueue();
        if (holder != null)
            holder.accept(inQueue);
        return useStub(inQueue, ()->{ body.run(); return null; });
    }
    
    public static  StubRecord useStub(FuncUnit1 holder, ComputeBody body) throws E {
        val inQueue = new ConsoleInQueue();
        if (holder != null)
            holder.accept(inQueue);
        return useStub(inQueue, ()->{ body.run(); return null; });
    }
    
    public static  StubRecord useStub(Stream inLines, RunBody body) throws E {
        return useStub(inLines, ()->{ body.run(); return null; });
    }
    
    public static  StubRecord useStub(ConsoleInQueue inQueue, RunBody body) throws E {
        return useStub(inQueue, ()->{ body.run(); return null; });
    }
    
    public static  StubRecord useStub(Stream inLines, ComputeBody body) throws E {
        val inQueue = new ConsoleInQueue(StreamPlus.from(inLines).toJavaList());
        return useStub(true, inQueue, body);
    }
    
    public static  StubRecord useStub(ConsoleInQueue inQueue, ComputeBody body) throws E {
        return useStub(false, inQueue, body);
    }
    private static  StubRecord useStub(boolean isInStreamDone, ConsoleInQueue inQueue, ComputeBody body) throws E {
        val stub = new Console.Stub(isInStreamDone, inQueue);
        val data = With(Env.refs.console.butWith(stub)).run(body);
        stub.flush();
        val outLines = stub.outLines().toImmutableList();
        val errLines = stub.errLines().toImmutableList();
        val inLines  = stub.recordedInLines().toImmutableList();
        val result   = new StubRecord(data, outLines, errLines, inLines);
        return result;
    }
    
    public static class System extends Instance {
        
        public static Instance instance = new System();
        
        private static class InPuller {
            private static final ConcurrentLinkedQueue lines = new ConcurrentLinkedQueue();
            private static final AtomicReference pullThread = new AtomicReference();
            static {
                if (pullThread.get() == null) {
                    if (pullThread.compareAndSet(null, createPullThread())) {
                        pullThread.get().start();
                    }
                }
            }
            
            private static Thread createPullThread() {
                Thread thread = new Thread(()->{
                    try (BufferedReader br = new BufferedReader(new InputStreamReader(java.lang.System.in))) {
                        br.lines()
                        .forEach(lines::add);
                    } catch (UncheckedIOException e) {
                    } catch (IOException e) {
                        throw new InterruptedRuntimeException(e);
                    }
                }, "SystemInReadThread");
                thread.setDaemon(false);
                return thread;
            }
            
            static String readln() {
                String line;
                while ((line = lines.poll()) == null);
                return line;
            }
            static String pollln() {
                return lines.poll();
            }
            static void stopRead() {
                pullThread.getAndUpdate(t -> {
                    t.interrupt();
                    try {
                        java.lang.System.in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return t;
                });
            }
        }
        
        
        @Override
        public Instance outPrint(Object text) {
            java.lang.System.out.print(text);
            return this;
        }
        @Override
        public Instance outPrintln(Object line) {
            java.lang.System.out.println(line);
            return this;
        }
        @Override
        public Instance outPrintf(String format, Object ... args) {
            java.lang.System.out.printf(format, args);
            return this;
        }
        @Override
        public Instance outPrintln() {
            java.lang.System.out.println();
            return this;
        }
        
        @Override
        public Instance errPrint(Object text) {
            java.lang.System.err.print(text);
            return this;
        }
        @Override
        public Instance errPrintln(Object line) {
            java.lang.System.err.println(line);
            return this;
        }
        @Override
        public Instance errPrintf(String format, Object ... args) {
            java.lang.System.err.printf(format, args);
            return this;
        }
        @Override
        public Instance errPrintln() {
            java.lang.System.err.println();
            return this;
        }
        
        @Override
        public String readln() {
            return InPuller.readln();
        }
        @Override
        public String pollln() {
            return InPuller.pollln();
        }
        
        public void stopRead() {
            InPuller.stopRead();
        }
        
    }
    
    public static class Stub extends Instance {
        
        public static String newEndValue() {
            return UUID.randomUUID().toString();
        }
        
        private final AtomicReference> outTexts = new AtomicReference<>(new ConcurrentLinkedQueue());
        private final AtomicReference> errTexts = new AtomicReference<>(new ConcurrentLinkedQueue());
        private final ConcurrentLinkedQueue outLines = new ConcurrentLinkedQueue();
        private final ConcurrentLinkedQueue errLines = new ConcurrentLinkedQueue();
        
        private final ConcurrentLinkedQueue     inLines  = new ConcurrentLinkedQueue();
        private final ConsoleInQueue                    inQueue;
        private final BlockingQueueIteratorPlus lines;
        private final FuncUnit1                 putInLine;
        
        public Stub() {
            this(false, new ConsoleInQueue());
        }
        public Stub(Collection inQueue) {
            this(true, new ConsoleInQueue(inQueue));
        }
        public Stub(ConsoleInQueue inQueue) {
            this(false, inQueue);
        }
        public Stub(boolean inStreamEnded, ConsoleInQueue inQueue) {
            this.inQueue   = (inQueue != null) ? inQueue : new ConsoleInQueue();
            this.lines     = new BlockingQueueIteratorPlus(this.inQueue.getEndValue(), this.inQueue);
            this.putInLine = f((String line) -> this.inQueue.put(line)).carelessly();
            
            if (inStreamEnded)
                this.inQueue.end();
        }
        
        public StreamPlus outLines() {
            return StreamPlus.from(outLines.stream());
        }
        public void clear() {
            clearOutLines();
            clearErrLines();
            clearInLines();
        }
        public void clearOutLines() {
            outLines.clear();
        }
        
        public StreamPlus errLines() {
            return StreamPlus.from(errLines.stream());
        }
        public void clearErrLines() {
            errLines.clear();
        }
        
        @Override
        public Instance outPrint(Object obj) {
            val text = String.valueOf(obj);
            outTexts.get().add(text);
            return this;
        }
        @Override
        public Instance outPrintln(Object line) {
            println(line, outTexts, outLines);
            return this;
        }
        @Override
        public Instance outPrintf(String format, Object ... args) {
            val line = String.format(format, args);
            return outPrintln(line);
        }
        @Override
        public Instance outPrintln() {
            return outPrintln("");
        }
        
        @Override
        public Instance errPrint(Object obj) {
            val text = String.valueOf(obj);
            errTexts.get().add(text);
            return this;
        }
        @Override
        public Instance errPrintln(Object line) {
            println(line, errTexts, errLines);
            return this;
        }
        @Override
        public Instance errPrintf(String format, Object ... args) {
            val line = String.format(format, args);
            return errPrintln(line);
        }
        @Override
        public Instance errPrintln() {
            return errPrintln("");
        }
        
        public void flush() {
            val outs = outTexts.get();
            if ((outs != null) && !outs.isEmpty()) {
                outPrintln();
            }
            
            val errs = errTexts.get();
            if ((errs != null) && !errs.isEmpty()) {
                errPrintln();
            }
        }
        
        private void println(Object line, 
                AtomicReference> texts,
                ConcurrentLinkedQueue                  lines) {
            texts.getAndUpdate(oldQuery -> {
                if (oldQuery.isEmpty()) {
                    val fullLine = String.valueOf(line);
                    val lineArray = fullLine.split("(\n|\r\n?)");
                    Arrays.stream(lineArray)
                          .forEach(lines::add);
                    return oldQuery;
                }
                
                val fullLine = oldQuery.stream().collect(Collectors.joining()) + String.valueOf(line);
                val lineArray = fullLine.split("(\n|\r\n?)");
                Arrays.stream(lineArray)
                      .forEach(lines::add);
                return new ConcurrentLinkedQueue();
            });
        }
        
        public Stub addInLines(String ... lines) {
            Arrays.stream(lines).forEach(putInLine);
            return this;
        }
        public Stub addInLines(Iterable lines) {
            lines.forEach(putInLine);
            return this;
        }
        public Stub addInLines(Iterator lines) {
            while (lines.hasNext()) {
                val line = lines.next();
                putInLine.accept(line);
            }
            return this;
        }
        public Stub addInLines(Stream lines) {
            lines.forEach(putInLine);
            return this;
        }
        
        public Stub endInStream() {
            inQueue.end();
            return this;
        }
        
        public StreamPlus remainingInLines() {
            return StreamPlus.from(lines.remainingValues());
        }
        public StreamPlus recordedInLines() {
            return StreamPlus.from(inLines.stream());
        }
        public void clearInLines() {
            inQueue.clear();
        }
        
        @Override
        public String readln() {
            String currentLine;
            try {
                currentLine = inQueue.take();
                inLines.add("" + currentLine);
                return currentLine;
            } catch (InterruptedException e) {
                throw ThrowFuncs.exceptionTransformer.get().apply(e);
            }
        }
        
        @Override
        public String pollln() {
            String currentLine = inQueue.poll();
            if (currentLine == null)
                return null;
            
            inLines.add("" + currentLine);
            return currentLine;
        }
        
        public void stopRead() {
            // Not sure what to do here.
        }
        
    }
    
}