/* * This file is part of ComputerCraft - http://www.computercraft.info * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ package dan200.computercraft.core.apis.handles; import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.LuaException; import javax.annotation.Nonnull; import java.io.BufferedReader; import java.io.Closeable; import java.io.IOException; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean; import static dan200.computercraft.core.apis.ArgumentHelper.optInt; public class EncodedReadableHandle extends HandleGeneric { private static final int BUFFER_SIZE = 8192; private BufferedReader m_reader; public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull Closeable closable ) { super( closable ); m_reader = reader; } public EncodedReadableHandle( @Nonnull BufferedReader reader ) { this( reader, reader ); } @Nonnull @Override public String[] getMethodNames() { return new String[] { "readLine", "readAll", "read", "close", }; } @Override public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException { switch( method ) { case 0: // readLine { checkOpen(); boolean withTrailing = optBoolean( args, 0, false ); try { String line = m_reader.readLine(); if( line != null ) { // While this is technically inaccurate, it's better than nothing if( withTrailing ) line += "\n"; return new Object[] { line }; } else { return null; } } catch( IOException e ) { return null; } } case 1: // readAll checkOpen(); try { StringBuilder result = new StringBuilder(); String line = m_reader.readLine(); while( line != null ) { result.append( line ); line = m_reader.readLine(); if( line != null ) { result.append( "\n" ); } } return new Object[] { result.toString() }; } catch( IOException e ) { return null; } case 2: // read checkOpen(); try { int count = optInt( args, 0, 1 ); if( count < 0 ) { // Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so // it seems best to remain somewhat consistent. throw new LuaException( "Cannot read a negative number of characters" ); } else if( count <= BUFFER_SIZE ) { // If we've got a small count, then allocate that and read it. char[] chars = new char[count]; int read = m_reader.read( chars ); return read < 0 ? null : new Object[] { new String( chars, 0, read ) }; } else { // If we've got a large count, read in bunches of 8192. char[] buffer = new char[BUFFER_SIZE]; // Read the initial set of characters, failing if none are read. int read = m_reader.read( buffer, 0, Math.min( buffer.length, count ) ); if( read < 0 ) return null; StringBuilder out = new StringBuilder( read ); int totalRead = read; out.append( buffer, 0, read ); // Otherwise read until we either reach the limit or we no longer consume // the full buffer. while( read >= BUFFER_SIZE && totalRead < count ) { read = m_reader.read( buffer, 0, Math.min( BUFFER_SIZE, count - totalRead ) ); if( read < 0 ) break; totalRead += read; out.append( buffer, 0, read ); } return new Object[] { out.toString() }; } } catch( IOException e ) { return null; } case 3: // close checkOpen(); close(); return null; default: return null; } } public static BufferedReader openUtf8( ReadableByteChannel channel ) { return open( channel, StandardCharsets.UTF_8 ); } public static BufferedReader open( ReadableByteChannel channel, Charset charset ) { // Create a charset decoder with the same properties as StreamDecoder does for // InputStreams: namely, replace everything instead of erroring. CharsetDecoder decoder = charset.newDecoder() .onMalformedInput( CodingErrorAction.REPLACE ) .onUnmappableCharacter( CodingErrorAction.REPLACE ); return new BufferedReader( Channels.newReader( channel, decoder, -1 ) ); } }