// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Razor
{
///
/// A that represents individual write operations as a sequence of strings when buffering.
/// The writer is backed by an unbuffered writer. When Flush or FlushAsync is invoked, the writer
/// copies all content to the unbuffered writier and switches to writing to the unbuffered writer for all further
/// write operations.
///
///
/// This is primarily designed to avoid creating large in-memory strings.
/// Refer to https://aspnetwebstack.codeplex.com/workitem/585 for more details.
///
public class RazorTextWriter : TextWriter
{
private static readonly Task _completedTask = Task.FromResult(0);
private readonly TextWriter _unbufferedWriter;
private readonly Encoding _encoding;
///
/// Creates a new instance of .
///
/// The to write output to when this instance
/// is no longer buffering.
/// The character in which the output is written.
public RazorTextWriter(TextWriter unbufferedWriter, Encoding encoding)
{
_unbufferedWriter = unbufferedWriter;
_encoding = encoding;
Buffer = new BufferEntryCollection();
}
///
public override Encoding Encoding
{
get { return _encoding; }
}
///
/// Gets a flag that determines if this instance of is buffering content.
///
public bool IsBuffering { get; private set; } = true;
///
/// A collection of entries buffered by this instance of .
///
public BufferEntryCollection Buffer { get; private set; }
///
public override void Write(char value)
{
if (IsBuffering)
{
Buffer.Add(value.ToString());
}
else
{
_unbufferedWriter.Write(value);
}
}
///
public override void Write([NotNull] char[] buffer, int index, int count)
{
if (index < 0)
{
throw new ArgumentOutOfRangeException("index");
}
if (count < 0)
{
throw new ArgumentOutOfRangeException("count");
}
if (buffer.Length - index < count)
{
throw new ArgumentOutOfRangeException("count");
}
if (IsBuffering)
{
Buffer.Add(buffer, index, count);
}
else
{
_unbufferedWriter.Write(buffer, index, count);
}
}
///
public override void Write(string value)
{
if (!string.IsNullOrEmpty(value))
{
if (IsBuffering)
{
Buffer.Add(value);
}
else
{
_unbufferedWriter.Write(value);
}
}
}
///
public override Task WriteAsync(char value)
{
if (IsBuffering)
{
Write(value);
return _completedTask;
}
else
{
return _unbufferedWriter.WriteAsync(value);
}
}
///
public override Task WriteAsync([NotNull] char[] buffer, int index, int count)
{
if (IsBuffering)
{
Write(buffer, index, count);
return _completedTask;
}
else
{
return _unbufferedWriter.WriteAsync(buffer, index, count);
}
}
///
public override Task WriteAsync(string value)
{
if (IsBuffering)
{
Write(value);
return _completedTask;
}
else
{
return _unbufferedWriter.WriteAsync(value);
}
}
///
public override void WriteLine()
{
if (IsBuffering)
{
Buffer.Add(Environment.NewLine);
}
else
{
_unbufferedWriter.WriteLine();
}
}
///
public override void WriteLine(string value)
{
if (IsBuffering)
{
Write(value);
WriteLine();
}
else
{
_unbufferedWriter.WriteLine(value);
}
}
///
public override Task WriteLineAsync(char value)
{
if (IsBuffering)
{
WriteLine(value);
return _completedTask;
}
else
{
return _unbufferedWriter.WriteLineAsync(value);
}
}
///
public override Task WriteLineAsync(char[] value, int start, int offset)
{
if (IsBuffering)
{
WriteLine(value, start, offset);
return _completedTask;
}
else
{
return _unbufferedWriter.WriteLineAsync(value, start, offset);
}
}
///
public override Task WriteLineAsync(string value)
{
if (IsBuffering)
{
WriteLine(value);
return _completedTask;
}
else
{
return _unbufferedWriter.WriteLineAsync(value);
}
}
///
public override Task WriteLineAsync()
{
if (IsBuffering)
{
WriteLine();
return _completedTask;
}
else
{
return _unbufferedWriter.WriteLineAsync();
}
}
///
/// Copies the buffered content to the unbuffered writer and invokes flush on it.
/// Additionally causes this instance to no longer buffer and direct all write operations
/// to the unbuffered writer.
///
public override void Flush()
{
if (IsBuffering)
{
IsBuffering = false;
CopyTo(_unbufferedWriter);
}
_unbufferedWriter.Flush();
}
///
/// Copies the buffered content to the unbuffered writer and invokes flush on it.
/// Additionally causes this instance to no longer buffer and direct all write operations
/// to the unbuffered writer.
///
/// A that represents the asynchronous copy and flush operations.
public override async Task FlushAsync()
{
if (IsBuffering)
{
IsBuffering = false;
await CopyToAsync(_unbufferedWriter);
}
await _unbufferedWriter.FlushAsync();
}
///
/// Copies the content of the to the instance.
///
/// The writer to copy contents to.
public void CopyTo(TextWriter writer)
{
var targetRazorTextWriter = writer as RazorTextWriter;
if (targetRazorTextWriter != null && targetRazorTextWriter.IsBuffering)
{
targetRazorTextWriter.Buffer.Add(Buffer);
}
else
{
// If the target writer is not buffering, we can directly copy to it's unbuffered writer
var targetWriter = targetRazorTextWriter != null ? targetRazorTextWriter._unbufferedWriter : writer;
WriteList(targetWriter, Buffer);
}
}
///
/// Copies the content of the to the specified instance.
///
/// The writer to copy contents to.
/// A task that represents the asynchronous copy operation.
public Task CopyToAsync(TextWriter writer)
{
var targetRazorTextWriter = writer as RazorTextWriter;
if (targetRazorTextWriter != null && targetRazorTextWriter.IsBuffering)
{
targetRazorTextWriter.Buffer.Add(Buffer);
}
else
{
// If the target writer is not buffering, we can directly copy to it's unbuffered writer
var targetWriter = targetRazorTextWriter != null ? targetRazorTextWriter._unbufferedWriter : writer;
return WriteListAsync(targetWriter, Buffer);
}
return _completedTask;
}
private static void WriteList(TextWriter writer, BufferEntryCollection values)
{
foreach (var value in values)
{
writer.Write(value);
}
}
private static async Task WriteListAsync(TextWriter writer, BufferEntryCollection values)
{
foreach (var value in values)
{
await writer.WriteAsync(value);
}
}
}
}