/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Internal;
namespace Microsoft.PowerShell.Commands.Internal.Format
{
///
/// OutCommand base implementation
/// it manages the formatting protocol and it writes to a generic
/// screen host
///
internal class OutCommandInner : ImplementationCommandBase
{
#region tracer
[TraceSource("format_out_OutCommandInner", "OutCommandInner")]
internal static PSTraceSource tracer = PSTraceSource.GetTracer("format_out_OutCommandInner", "OutCommandInner");
#endregion tracer
internal override void BeginProcessing()
{
base.BeginProcessing();
_formatObjectDeserializer = new FormatObjectDeserializer(this.TerminatingErrorContext);
// hook up the event handlers for the context manager object
_ctxManager.contextCreation = new FormatMessagesContextManager.FormatContextCreationCallback(this.CreateOutputContext);
_ctxManager.fs = new FormatMessagesContextManager.FormatStartCallback(this.ProcessFormatStart);
_ctxManager.fe = new FormatMessagesContextManager.FormatEndCallback(this.ProcessFormatEnd);
_ctxManager.gs = new FormatMessagesContextManager.GroupStartCallback(this.ProcessGroupStart);
_ctxManager.ge = new FormatMessagesContextManager.GroupEndCallback(this.ProcessGroupEnd);
_ctxManager.payload = new FormatMessagesContextManager.PayloadCallback(this.ProcessPayload);
}
///
/// execution entry point override
/// we assume that a LineOutput interface instance already has been acquired
///
/// IMPORTANT: it assumes the presence of a pre-processing formatting command
///
internal override void ProcessRecord()
{
PSObject so = this.ReadObject();
if (so == null || so == AutomationNull.Value)
return;
// try to process the object
if (ProcessObject(so))
return;
// send to the formatter for preprocessing
Array results = ApplyFormatting(so);
if (results != null)
{
foreach (object r in results)
{
PSObject obj = PSObjectHelper.AsPSObject(r);
obj.IsHelpObject = so.IsHelpObject;
ProcessObject(obj);
}
}
}
internal override void EndProcessing()
{
base.EndProcessing();
if (_command != null)
{
// shut down the command processor, if we ever used it
Array results = _command.ShutDown();
if (results != null)
{
foreach (object o in results)
{
ProcessObject(PSObjectHelper.AsPSObject(o));
}
}
}
if (this.LineOutput.RequiresBuffering)
{
// we need to notify the interface implementor that
// we are about to do the playback
LineOutput.DoPlayBackCall playBackCall = new LineOutput.DoPlayBackCall(this.DrainCache);
this.LineOutput.ExecuteBufferPlayBack(playBackCall);
}
else
{
// we drain the cache ourselves
DrainCache();
}
}
private void DrainCache()
{
if (_cache != null)
{
// drain the cache, since we are shutting down
List unprocessedObjects = _cache.Drain();
if (unprocessedObjects != null)
{
foreach (object obj in unprocessedObjects)
{
_ctxManager.Process(obj);
}
}
}
}
private bool ProcessObject(PSObject so)
{
object o = _formatObjectDeserializer.Deserialize(so);
//Console.WriteLine("OutCommandInner.Execute() retrieved object {0}, of type {1}", o.ToString(), o.GetType());
if (NeedsPreprocessing(o))
{
return false;
}
// instantiate the cache if not done yet
if (_cache == null)
{
_cache = new FormattedObjectsCache(this.LineOutput.RequiresBuffering);
}
// no need for formatting, just process the object
FormatStartData formatStart = o as FormatStartData;
if (formatStart != null)
{
// get autosize flag from object
// turn on group caching
if (formatStart.autosizeInfo != null)
{
FormattedObjectsCache.ProcessCachedGroupNotification callBack = new FormattedObjectsCache.ProcessCachedGroupNotification(ProcessCachedGroup);
_cache.EnableGroupCaching(callBack, formatStart.autosizeInfo.objectCount);
}
else
{
// If the format info doesn't define column widths, then auto-size based on the first ten elements
TableHeaderInfo headerInfo = formatStart.shapeInfo as TableHeaderInfo;
if ((headerInfo != null) &&
(headerInfo.tableColumnInfoList.Count > 0) &&
(headerInfo.tableColumnInfoList[0].width == 0))
{
FormattedObjectsCache.ProcessCachedGroupNotification callBack = new FormattedObjectsCache.ProcessCachedGroupNotification(ProcessCachedGroup);
_cache.EnableGroupCaching(callBack, TimeSpan.FromMilliseconds(300));
}
}
}
//Console.WriteLine("OutCommandInner.Execute() calling ctxManager.Process({0})",o.ToString());
List info = _cache.Add((PacketInfoData)o);
if (info != null)
{
for (int k = 0; k < info.Count; k++)
_ctxManager.Process(info[k]);
}
return true;
}
///
/// helper to return what shape we have to use to format the output
///
private FormatShape ActiveFormattingShape
{
get
{
// we assume that the format context
// contains the information
FormatShape shape = FormatShape.Table; // default
FormatOutputContext foc = this.FormatContext;
if (foc == null || foc.Data.shapeInfo == null)
return shape;
if (foc.Data.shapeInfo is TableHeaderInfo)
return FormatShape.Table;
if (foc.Data.shapeInfo is ListViewHeaderInfo)
return FormatShape.List;
if (foc.Data.shapeInfo is WideViewHeaderInfo)
return FormatShape.Wide;
if (foc.Data.shapeInfo is ComplexViewHeaderInfo)
return FormatShape.Complex;
return shape;
}
}
protected override void InternalDispose()
{
base.InternalDispose();
if (_command != null)
{
_command.Dispose();
_command = null;
}
}
///
/// enum describing the state for the output finite state machine
///
private enum FormattingState
{
///
/// we are in the clear state: no formatting in process
///
Reset,
///
/// we received a Format Start message, but we are not inside a group
///
Formatting,
///
/// we are inside a group because we received a Group Start
///
InsideGroup
}
///
/// toggle to signal if we are in a formatting sequence
///
private FormattingState _currentFormattingState = FormattingState.Reset;
///
/// instance of a command wrapper to execute the
/// default formatter when needed
///
private CommandWrapper _command;
///
/// enumeration to drive the preprocessing stage
///
private enum PreprocessingState { raw, processed, error }
///
/// test if an object coming from the pipeline needs to be
/// preprocessed by the default formatter
///
/// object to examine for formatting
/// whether the object needs to be shunted to preprocessing
private bool NeedsPreprocessing(object o)
{
FormatEntryData fed = o as FormatEntryData;
if (fed != null)
{
// we got an already pre-processed object
if (!fed.outOfBand)
{
// we allow out of band data in any state
ValidateCurrentFormattingState(FormattingState.InsideGroup, o);
}
return false;
}
else if (o is FormatStartData)
{
// when encountering FormatStartDate out of sequence,
// pretend that the previous formatting directives were properly closed
if (_currentFormattingState == FormattingState.InsideGroup)
{
this.EndProcessing();
this.BeginProcessing();
}
// we got a Fs message, we enter a sequence
ValidateCurrentFormattingState(FormattingState.Reset, o);
_currentFormattingState = FormattingState.Formatting;
return false;
}
else if (o is FormatEndData)
{
// we got a Fe message, we exit a sequence
ValidateCurrentFormattingState(FormattingState.Formatting, o);
_currentFormattingState = FormattingState.Reset;
return false;
}
else if (o is GroupStartData)
{
ValidateCurrentFormattingState(FormattingState.Formatting, o);
_currentFormattingState = FormattingState.InsideGroup;
return false;
}
else if (o is GroupEndData)
{
ValidateCurrentFormattingState(FormattingState.InsideGroup, o);
_currentFormattingState = FormattingState.Formatting;
return false;
}
// this is a raw object
return true;
}
private void ValidateCurrentFormattingState(FormattingState expectedFormattingState, object obj)
{
// check if we are in the expected formatting state
if (_currentFormattingState != expectedFormattingState)
{
// we are not in the expected state, some message is out of sequence,
// need to abort the command
string violatingCommand = "format-*";
StartData sdObj = obj as StartData;
if (sdObj != null)
{
if (sdObj.shapeInfo is WideViewHeaderInfo)
{
violatingCommand = "format-wide";
}
else if (sdObj.shapeInfo is TableHeaderInfo)
{
violatingCommand = "format-table";
}
else if (sdObj.shapeInfo is ListViewHeaderInfo)
{
violatingCommand = "format-list";
}
else if (sdObj.shapeInfo is ComplexViewHeaderInfo)
{
violatingCommand = "format-complex";
}
}
string msg = StringUtil.Format(FormatAndOut_out_xxx.OutLineOutput_OutOfSequencePacket,
obj.GetType().FullName, violatingCommand);
ErrorRecord errorRecord = new ErrorRecord(
new InvalidOperationException(),
"ConsoleLineOutputOutOfSequencePacket",
ErrorCategory.InvalidData,
null);
errorRecord.ErrorDetails = new ErrorDetails(msg);
this.TerminatingErrorContext.ThrowTerminatingError(errorRecord);
}
}
///
/// shunt object to the formatting pipeline for preprocessing
///
/// object to be preprocessed
/// array of objects returned by the preprocessing step
private Array ApplyFormatting(object o)
{
if (_command == null)
{
_command = new CommandWrapper();
_command.Initialize(this.OuterCmdlet().Context, "format-default", typeof(FormatDefaultCommand));
}
return _command.Process(o);
}
///
/// class factory for output context
///
/// parent context in the stack
/// fromat info data received from the pipeline
///
private FormatMessagesContextManager.OutputContext CreateOutputContext(
FormatMessagesContextManager.OutputContext parentContext,
FormatInfoData formatInfoData)
{
FormatStartData formatStartData = formatInfoData as FormatStartData;
// initialize the format context
if (formatStartData != null)
{
FormatOutputContext foc = new FormatOutputContext(parentContext, formatStartData);
return foc;
}
GroupStartData gsd = formatInfoData as GroupStartData;
// we are starting a group, initialize the group context
if (gsd != null)
{
GroupOutputContext goc = null;
switch (ActiveFormattingShape)
{
case FormatShape.Table:
{
goc = new TableOutputContext(this, parentContext, gsd);
break;
}
case FormatShape.List:
{
goc = new ListOutputContext(this, parentContext, gsd);
break;
}
case FormatShape.Wide:
{
goc = new WideOutputContext(this, parentContext, gsd);
break;
}
case FormatShape.Complex:
{
goc = new ComplexOutputContext(this, parentContext, gsd);
break;
}
default:
{
Diagnostics.Assert(false, "Invalid shape. This should never happen");
}
break;
}
goc.Initialize();
return goc;
}
return null;
}
///
/// callback for Fs processing
///
/// the context containing the Fs entry
private void ProcessFormatStart(FormatMessagesContextManager.OutputContext c)
{
// we just add an empty line to the display
this.LineOutput.WriteLine("");
}
///
/// callback for Fe processing
///
/// Fe notification message
/// current context, with Fs in it
private void ProcessFormatEnd(FormatEndData fe, FormatMessagesContextManager.OutputContext c)
{
//Console.WriteLine("ProcessFormatEnd");
// we just add an empty line to the display
this.LineOutput.WriteLine("");
}
///
/// callback for Gs processing
///
/// the context containing the Gs entry
private void ProcessGroupStart(FormatMessagesContextManager.OutputContext c)
{
//Console.WriteLine("ProcessGroupStart");
GroupOutputContext goc = (GroupOutputContext)c;
if (goc.Data.groupingEntry != null)
{
_lo.WriteLine("");
ComplexWriter writer = new ComplexWriter();
writer.Initialize(_lo, _lo.ColumnNumber);
writer.WriteObject(goc.Data.groupingEntry.formatValueList);
this.LineOutput.WriteLine("");
}
goc.GroupStart();
}
///
/// callback for Ge processing
///
/// Ge notification message
/// current context, with Gs in it
private void ProcessGroupEnd(GroupEndData ge, FormatMessagesContextManager.OutputContext c)
{
//Console.WriteLine("ProcessGroupEnd");
GroupOutputContext goc = (GroupOutputContext)c;
goc.GroupEnd();
this.LineOutput.WriteLine("");
}
///
/// process the current payload object
///
/// FormatEntryData to process
/// currently active context
private void ProcessPayload(FormatEntryData fed, FormatMessagesContextManager.OutputContext c)
{
// we assume FormatEntryData as a standard wrapper
if (fed == null)
{
PSTraceSource.NewArgumentNullException("fed");
}
if (fed.formatEntryInfo == null)
{
PSTraceSource.NewArgumentNullException("fed.formatEntryInfo");
}
WriteStreamType oldWSState = _lo.WriteStream;
try
{
_lo.WriteStream = fed.writeStream;
if (c == null)
{
ProcessOutOfBandPayload(fed);
}
else
{
GroupOutputContext goc = (GroupOutputContext)c;
goc.ProcessPayload(fed);
}
}
finally
{
_lo.WriteStream = oldWSState;
}
}
private void ProcessOutOfBandPayload(FormatEntryData fed)
{
// try if it is raw text
RawTextFormatEntry rte = fed.formatEntryInfo as RawTextFormatEntry;
if (rte != null)
{
if (fed.isHelpObject)
{
ComplexWriter complexWriter = new ComplexWriter();
complexWriter.Initialize(_lo, _lo.ColumnNumber);
complexWriter.WriteString(rte.text);
}
else
{
_lo.WriteLine(rte.text);
}
return;
}
// try if it is a complex entry
ComplexViewEntry cve = fed.formatEntryInfo as ComplexViewEntry;
if (cve != null && cve.formatValueList != null)
{
ComplexWriter complexWriter = new ComplexWriter();
complexWriter.Initialize(_lo, int.MaxValue);
complexWriter.WriteObject(cve.formatValueList);
return;
}
// try if it is a list view
ListViewEntry lve = fed.formatEntryInfo as ListViewEntry;
if (lve != null && lve.listViewFieldList != null)
{
ListWriter listWriter = new ListWriter();
_lo.WriteLine("");
string[] properties = ListOutputContext.GetProperties(lve);
listWriter.Initialize(properties, _lo.ColumnNumber, _lo.DisplayCells);
string[] values = ListOutputContext.GetValues(lve);
listWriter.WriteProperties(values, _lo);
_lo.WriteLine("");
return;
}
}
///
/// the screen host associated with this outputter
///
private LineOutput _lo = null;
internal LineOutput LineOutput
{
set { _lo = value; }
get { return _lo; }
}
private ShapeInfo ShapeInfoOnFormatContext
{
get
{
FormatOutputContext foc = this.FormatContext;
if (foc == null)
return null;
return foc.Data.shapeInfo;
}
}
///
/// retrieve the active FormatOutputContext on the stack
/// by walking up to the top of the stack
///
private FormatOutputContext FormatContext
{
get
{
for (FormatMessagesContextManager.OutputContext oc = _ctxManager.ActiveOutputContext; oc != null; oc = oc.ParentContext)
{
FormatOutputContext foc = oc as FormatOutputContext;
if (foc != null)
return foc;
}
return null;
}
}
///
/// context manager instance to guide the message traversal
///
private FormatMessagesContextManager _ctxManager = new FormatMessagesContextManager();
private FormattedObjectsCache _cache = null;
///
/// handler for processing the caching notification and responsible for
/// setting the value of the formatting hint
///
///
///
private void ProcessCachedGroup(FormatStartData formatStartData, List objects)
{
_formattingHint = null;
TableHeaderInfo thi = formatStartData.shapeInfo as TableHeaderInfo;
if (thi != null)
{
ProcessCachedGroupOnTable(thi, objects);
return;
}
WideViewHeaderInfo wvhi = formatStartData.shapeInfo as WideViewHeaderInfo;
if (wvhi != null)
{
ProcessCachedGroupOnWide(wvhi, objects);
return;
}
}
private void ProcessCachedGroupOnTable(TableHeaderInfo thi, List objects)
{
if (thi.tableColumnInfoList.Count == 0)
return;
int[] widths = new int[thi.tableColumnInfoList.Count];
for (int k = 0; k < thi.tableColumnInfoList.Count; k++)
{
string label = thi.tableColumnInfoList[k].label;
if (string.IsNullOrEmpty(label))
label = thi.tableColumnInfoList[k].propertyName;
if (string.IsNullOrEmpty(label))
widths[k] = 0;
else
widths[k] = _lo.DisplayCells.Length(label);
}
int cellCount; // scratch variable
foreach (PacketInfoData o in objects)
{
FormatEntryData fed = o as FormatEntryData;
if (fed == null)
continue;
TableRowEntry tre = fed.formatEntryInfo as TableRowEntry;
int kk = 0;
foreach (FormatPropertyField fpf in tre.formatPropertyFieldList)
{
cellCount = _lo.DisplayCells.Length(fpf.propertyValue);
if (widths[kk] < cellCount)
widths[kk] = cellCount;
kk++;
}
}
TableFormattingHint hint = new TableFormattingHint();
hint.columnWidths = widths;
_formattingHint = hint;
}
private void ProcessCachedGroupOnWide(WideViewHeaderInfo wvhi, List objects)
{
if (wvhi.columns != 0)
{
// columns forced on the client
return;
}
int maxLen = 0;
int cellCount; // scratch variable
foreach (PacketInfoData o in objects)
{
FormatEntryData fed = o as FormatEntryData;
if (fed == null)
continue;
WideViewEntry wve = fed.formatEntryInfo as WideViewEntry;
FormatPropertyField fpf = wve.formatPropertyField as FormatPropertyField;
if (!string.IsNullOrEmpty(fpf.propertyValue))
{
cellCount = _lo.DisplayCells.Length(fpf.propertyValue);
if (cellCount > maxLen)
maxLen = cellCount;
}
}
WideFormattingHint hint = new WideFormattingHint();
hint.maxWidth = maxLen;
_formattingHint = hint;
}
///
/// base class for all the formatting hints
///
private abstract class FormattingHint
{
}
///
/// hint for format-table
///
private sealed class TableFormattingHint : FormattingHint
{
internal int[] columnWidths = null;
}
///
/// hint for format-wide
///
private sealed class WideFormattingHint : FormattingHint
{
internal int maxWidth = 0;
}
///
/// variable holding the autosize hint (set by the caching code and reset by the hint consumer
///
private FormattingHint _formattingHint = null;
///
/// helper for consuming the formatting hint
///
///
private FormattingHint RetrieveFormattingHint()
{
FormattingHint fh = _formattingHint;
_formattingHint = null;
return fh;
}
private FormatObjectDeserializer _formatObjectDeserializer;
///
/// context for the outer scope of the format sequence
///
private class FormatOutputContext : FormatMessagesContextManager.OutputContext
{
///
/// construct a context to push on the stack
///
/// parent context in the stack
/// format data to put in the context
internal FormatOutputContext(FormatMessagesContextManager.OutputContext parentContext, FormatStartData formatData)
: base(parentContext)
{
Data = formatData;
}
///
/// retrieve the format data in the context
///
internal FormatStartData Data { get; } = null;
}
///
/// context for the currently active group
///
private abstract class GroupOutputContext : FormatMessagesContextManager.OutputContext
{
///
/// construct a context to push on the stack
///
internal GroupOutputContext(OutCommandInner cmd,
FormatMessagesContextManager.OutputContext parentContext,
GroupStartData formatData)
: base(parentContext)
{
InnerCommand = cmd;
Data = formatData;
}
///
/// called at creation time, overrides will initialize here, e.g.
/// column widths, etc.
///
internal virtual void Initialize() { }
///
/// called when a group of data is started, overridden will do
/// things such as headers, etc...
///
internal virtual void GroupStart() { }
///
/// called when the end of a group is reached, overrides will do
/// things such as group footers
///
internal virtual void GroupEnd() { }
///
/// called when there is an entry to process, overrides will do
/// things such as writing a row in a table
///
/// FormatEntryData to process
internal virtual void ProcessPayload(FormatEntryData fed) { }
///
/// retrieve the format data in the context
///
internal GroupStartData Data { get; } = null;
protected OutCommandInner InnerCommand { get; }
}
private class TableOutputContextBase : GroupOutputContext
{
///
/// construct a context to push on the stack
///
/// reference to the OutCommandInner instance who owns this instance
/// parent context in the stack
/// format data to put in the context
internal TableOutputContextBase(OutCommandInner cmd,
FormatMessagesContextManager.OutputContext parentContext,
GroupStartData formatData)
: base(cmd, parentContext, formatData)
{
}
///
/// Get the table writer for this context
///
protected TableWriter Writer { get { return _tableWriter; } }
///
/// helper class to properly write a table using text output
///
private TableWriter _tableWriter = new TableWriter();
}
private sealed class TableOutputContext : TableOutputContextBase
{
///
/// construct a context to push on the stack
///
/// reference to the OutCommandInner instance who owns this instance
/// parent context in the stack
/// format data to put in the context
internal TableOutputContext(OutCommandInner cmd,
FormatMessagesContextManager.OutputContext parentContext,
GroupStartData formatData)
: base(cmd, parentContext, formatData)
{
}
///
/// initialize column widths
///
internal override void Initialize()
{
TableFormattingHint tableHint = this.InnerCommand.RetrieveFormattingHint() as TableFormattingHint;
int[] columnWidthsHint = null;
if (tableHint != null)
{
columnWidthsHint = tableHint.columnWidths;
}
int columnsOnTheScreen = this.InnerCommand._lo.ColumnNumber;
// Tables need to use spaces for padding to maintain table look even if console window is resized.
// For all other output, we use int.MaxValue if the user didn't explicitly specify a width.
// If we detect that int.MaxValue is used, first we try to get the current console window width.
// However, if we can't read that (for example, implicit remoting has no console window), we default
// to something reasonable: 120 columns.
if (columnsOnTheScreen == int.MaxValue)
{
try
{
columnsOnTheScreen = Console.WindowWidth;
}
catch
{
columnsOnTheScreen = 120;
}
}
int columns = this.CurrentTableHeaderInfo.tableColumnInfoList.Count;
if (columns == 0)
{
return;
}
// create arrays for widths and alignment
int[] columnWidths = new int[columns];
int[] alignment = new int[columns];
int k = 0;
foreach (TableColumnInfo tci in this.CurrentTableHeaderInfo.tableColumnInfoList)
{
columnWidths[k] = (columnWidthsHint != null) ? columnWidthsHint[k] : tci.width;
alignment[k] = tci.alignment;
k++;
}
this.Writer.Initialize(0, columnsOnTheScreen, columnWidths, alignment, this.CurrentTableHeaderInfo.hideHeader);
}
///
/// write the headers
///
internal override void GroupStart()
{
int columns = this.CurrentTableHeaderInfo.tableColumnInfoList.Count;
if (columns == 0)
{
return;
}
string[] properties = new string[columns];
int k = 0;
foreach (TableColumnInfo tci in this.CurrentTableHeaderInfo.tableColumnInfoList)
{
properties[k++] = tci.label ?? tci.propertyName;
}
this.Writer.GenerateHeader(properties, this.InnerCommand._lo);
}
///
/// write a row into the table
///
/// FormatEntryData to process
internal override void ProcessPayload(FormatEntryData fed)
{
int headerColumns = this.CurrentTableHeaderInfo.tableColumnInfoList.Count;
if (headerColumns == 0)
{
return;
}
TableRowEntry tre = fed.formatEntryInfo as TableRowEntry;
// need to make sure we have matching counts: the header count will have to prevail
string[] values = new string[headerColumns];
int[] alignment = new int[headerColumns];
int fieldCount = tre.formatPropertyFieldList.Count;
for (int k = 0; k < headerColumns; k++)
{
if (k < fieldCount)
{
values[k] = tre.formatPropertyFieldList[k].propertyValue;
alignment[k] = tre.formatPropertyFieldList[k].alignment;
}
else
{
values[k] = "";
alignment[k] = TextAlignment.Left; // hard coded default
}
}
this.Writer.GenerateRow(values, this.InnerCommand._lo, tre.multiLine, alignment, InnerCommand._lo.DisplayCells);
}
private TableHeaderInfo CurrentTableHeaderInfo
{
get
{
return (TableHeaderInfo)this.InnerCommand.ShapeInfoOnFormatContext;
}
}
}
private sealed class ListOutputContext : GroupOutputContext
{
///
/// construct a context to push on the stack
///
/// reference to the OutCommandInner instance who owns this instance
/// parent context in the stack
/// format data to put in the context
internal ListOutputContext(OutCommandInner cmd,
FormatMessagesContextManager.OutputContext parentContext,
GroupStartData formatData)
: base(cmd, parentContext, formatData)
{
}
///
/// initialize column widths
///
internal override void Initialize()
{
}
private void InternalInitialize(ListViewEntry lve)
{
_properties = GetProperties(lve);
_listWriter.Initialize(_properties, this.InnerCommand._lo.ColumnNumber, InnerCommand._lo.DisplayCells);
}
internal static string[] GetProperties(ListViewEntry lve)
{
StringCollection props = new StringCollection();
foreach (ListViewField lvf in lve.listViewFieldList)
{
props.Add(lvf.label ?? lvf.propertyName);
}
if (props.Count == 0)
return null;
string[] retVal = new string[props.Count];
props.CopyTo(retVal, 0);
return retVal;
}
internal static string[] GetValues(ListViewEntry lve)
{
StringCollection vals = new StringCollection();
foreach (ListViewField lvf in lve.listViewFieldList)
{
vals.Add(lvf.formatPropertyField.propertyValue);
}
if (vals.Count == 0)
return null;
string[] retVal = new string[vals.Count];
vals.CopyTo(retVal, 0);
return retVal;
}
///
/// write the headers
///
internal override void GroupStart()
{
this.InnerCommand._lo.WriteLine("");
}
///
/// write a row into the list
///
/// FormatEntryData to process
internal override void ProcessPayload(FormatEntryData fed)
{
ListViewEntry lve = fed.formatEntryInfo as ListViewEntry;
InternalInitialize(lve);
string[] values = GetValues(lve);
_listWriter.WriteProperties(values, this.InnerCommand._lo);
this.InnerCommand._lo.WriteLine("");
}
///
/// property list currently active
///
private string[] _properties = null;
///
/// writer to do the actual formatting
///
private ListWriter _listWriter = new ListWriter();
}
private sealed class WideOutputContext : TableOutputContextBase
{
///
/// construct a context to push on the stack
///
/// reference to the OutCommandInner instance who owns this instance
/// parent context in the stack
/// format data to put in the context
internal WideOutputContext(OutCommandInner cmd,
FormatMessagesContextManager.OutputContext parentContext,
GroupStartData formatData)
: base(cmd, parentContext, formatData)
{
}
private StringValuesBuffer _buffer = null;
///
/// initialize column widths
///
internal override void Initialize()
{
// set the hard wider default, to be used if no other info is available
int itemsPerRow = 2;
// get the header info and the view hint
WideFormattingHint hint = this.InnerCommand.RetrieveFormattingHint() as WideFormattingHint;
// give a preference to the hint, if there
if (hint != null && hint.maxWidth > 0)
{
itemsPerRow = TableWriter.ComputeWideViewBestItemsPerRowFit(hint.maxWidth, this.InnerCommand._lo.ColumnNumber);
}
else if (this.CurrentWideHeaderInfo.columns > 0)
{
itemsPerRow = this.CurrentWideHeaderInfo.columns;
}
// create a buffer object to hold partial rows
_buffer = new StringValuesBuffer(itemsPerRow);
// initialize the writer
int[] columnWidths = new int[itemsPerRow];
int[] alignment = new int[itemsPerRow];
for (int k = 0; k < itemsPerRow; k++)
{
columnWidths[k] = 0; // autosize
alignment[k] = TextAlignment.Left;
}
this.Writer.Initialize(0, this.InnerCommand._lo.ColumnNumber, columnWidths, alignment, false);
}
///
/// write the headers
///
internal override void GroupStart()
{
this.InnerCommand._lo.WriteLine("");
}
///
/// called when the end of a group is reached, flush the
/// write buffer
///
internal override void GroupEnd()
{
WriteStringBuffer();
}
///
/// write a row into the table
///
/// FormatEntryData to process
internal override void ProcessPayload(FormatEntryData fed)
{
WideViewEntry wve = fed.formatEntryInfo as WideViewEntry;
FormatPropertyField fpf = wve.formatPropertyField as FormatPropertyField;
_buffer.Add(fpf.propertyValue);
if (_buffer.IsFull)
{
WriteStringBuffer();
}
}
private WideViewHeaderInfo CurrentWideHeaderInfo
{
get
{
return (WideViewHeaderInfo)this.InnerCommand.ShapeInfoOnFormatContext;
}
}
private void WriteStringBuffer()
{
if (_buffer.IsEmpty)
{
return;
}
string[] values = new string[_buffer.Length];
for (int k = 0; k < values.Length; k++)
{
if (k < _buffer.CurrentCount)
values[k] = _buffer[k];
else
values[k] = "";
}
this.Writer.GenerateRow(values, this.InnerCommand._lo, false, null, InnerCommand._lo.DisplayCells);
_buffer.Reset();
}
///
/// helper class to accumulate the display values so that when the end
/// of a line is reached, a full line can be composed
///
private class StringValuesBuffer
{
///
/// construct the buffer
///
/// number of entries to cache
internal StringValuesBuffer(int size)
{
_arr = new string[size];
Reset();
}
///
/// get the size of the buffer
///
internal int Length { get { return _arr.Length; } }
///
/// get the current number of entries in the buffer
///
internal int CurrentCount { get { return _lastEmptySpot; } }
///
/// check if the buffer is full
///
internal bool IsFull
{
get { return _lastEmptySpot == _arr.Length; }
}
///
/// check if the buffer is empty
///
internal bool IsEmpty
{
get { return _lastEmptySpot == 0; }
}
///
/// indexer to access the k-th item in the buffer
///
internal string this[int k] { get { return _arr[k]; } }
///
/// add an item to the buffer
///
/// string to add
internal void Add(string s)
{
_arr[_lastEmptySpot++] = s;
}
///
/// reset the buffer
///
internal void Reset()
{
_lastEmptySpot = 0;
for (int k = 0; k < _arr.Length; k++)
_arr[k] = null;
}
private string[] _arr;
private int _lastEmptySpot;
}
}
private sealed class ComplexOutputContext : GroupOutputContext
{
///
/// construct a context to push on the stack
///
/// reference to the OutCommandInner instance who owns this instance
/// parent context in the stack
/// format data to put in the context
internal ComplexOutputContext(OutCommandInner cmd,
FormatMessagesContextManager.OutputContext parentContext,
GroupStartData formatData)
: base(cmd, parentContext, formatData)
{
}
internal override void Initialize()
{
_writer.Initialize(this.InnerCommand._lo,
this.InnerCommand._lo.ColumnNumber);
}
///
/// write a row into the list
///
/// FormatEntryData to process
internal override void ProcessPayload(FormatEntryData fed)
{
ComplexViewEntry cve = fed.formatEntryInfo as ComplexViewEntry;
if (cve == null || cve.formatValueList == null)
return;
_writer.WriteObject(cve.formatValueList);
}
private ComplexWriter _writer = new ComplexWriter();
}
}
}