init: 导入项目到 meswork Gitea

This commit is contained in:
XingCheng3
2026-05-29 10:07:05 +08:00
commit ad9a4106fb
3582 changed files with 460312 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
//----------------------------------------------------------------
// Copyright (C) 2000-2001 Microsoft Corporation
// All rights reserved.
//
// This source code is intended only as a supplement to Microsoft
// Development Tools and/or on-line documentation. See these other
// materials for detailed information regarding Microsoft code samples.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR
// FITNESS FOR A PARTICULAR PURPOSE.
//----------------------------------------------------------------
namespace SystemFramework
{
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
/// <summary>
/// A class to help with error checking and automatic logging
/// of asserts and conditional checks.
/// <remarks>
/// This class works with displays system assert dialogs
/// as well as writing to the application log with the
/// ApplicationLog class. There is no instance data associated
/// with this class.
/// </remarks>
/// </summary>
public class ApplicationAssert
{
// #if !DEBUG
/// <value>
/// A LineNumber constant to be used when not in a debug build
/// so that ApplicationAssert. LineNumber is always a valid expression.
/// <remarks>
/// This allows us to pass ApplicationAssert.LineNumber with good debug
/// functionality and minimal runtime overhead.
/// </remarks>
/// </value>
// public const int LineNumber = 0;
// #else
static string currentTrace;
/// <value>Property LineNumber is used to get the current line number in the calling function.</value>
/// <remarks>
/// This should be called in a parameter list to get accurate
/// information about the line number before the Check* functions
/// are called. If we wait until the Check* functions themselves
/// to retrieve this information, then the stack trace indicates
/// the next executable line, which is only marginally useful
/// information. This function is compiled out in debug builds in
/// favor of the LineNumber constant.
/// Returns LineNumber, or 0 on failure.
/// </remarks>
public static int LineNumber
{
get
{
try
{
//
// Get the trace information with file information by skipping
// this function and then reading the top stack frame.
//
return (new StackTrace(1, true)).GetFrame(0).GetFileLineNumber();
}
catch
{
}
return 0;
}
}
//#endif
/// <summary>
/// Check the given condition and show an assert dialog when the
/// desktop is interactive.
/// <remarks>
/// Log the assertion at a warning level in case the desktop is not
/// interactive. The text will always contain full stack trace
/// information and will show the location of the error condition if
/// the source code is available.
/// </remarks>
/// <param name="condition">An expression to be tested for True</param>
/// <param name="errorText">The message to display</param>
/// <param name="lineNumber">
/// The line of the current error in the function. See
/// GenerateStackTrace for more information.
/// </param>
/// </summary>
//[ConditionalAttribute("DEBUG")]
public static void Check(bool condition, String errorText, int lineNumber)
{
if ( !condition )
{
String detailMessage = String.Empty;
StringBuilder strBuilder;
GenerateStackTrace(lineNumber);
detailMessage = currentTrace;
strBuilder = new StringBuilder();
strBuilder.Append("Assert: ").Append("\r\n").Append(errorText).Append("\r\n").Append(detailMessage);
ApplicationLog.WriteLog(strBuilder.ToString());
System.Diagnostics.Debug.Fail(errorText, detailMessage);
}
}
/// <summary>
///
/// </summary>
/// <param name="errorText"></param>
/// <param name="lineNumber"></param>
/// <returns></returns>
public static string GetDetailMessage(String errorText, int lineNumber)
{
String detailMessage = String.Empty;
StringBuilder strBuilder;
GenerateStackTrace(lineNumber);
detailMessage = currentTrace;
strBuilder = new StringBuilder();
strBuilder.Append("Assert: ").Append("\r\n").Append(errorText).Append("\r\n").Append(detailMessage);
return strBuilder.ToString();
}
/// <summary>
/// Verify that a required condition holds.
/// <remarks>
/// Show an assert dialog in a DEBUG build before throwing an
/// ApplicationException. It is assumed that the exception will be
/// handled or logged, so this does not log a warning for the assertion
/// like the Check function, which does not actually throw.
/// </remarks>
/// <param name="condition">An expression to be tested for True</param>
/// <param name="errorText">The message to display</param>
/// <param name="lineNumber">
/// The line of the current error in the function. See
/// GenerateStackTrace for more information.
/// </param>
/// <exception class="System.ApplicationException">
/// The checked condition failed.
/// </exception>
/// </summary>
public static void CheckCondition(bool condition, String errorText, int lineNumber)
{
//Test the condition
if ( !condition )
{
//Assert and throw if the condition is not met
String detailMessage;
GenerateStackTrace(lineNumber);
detailMessage = currentTrace;
Debug.Fail(errorText, detailMessage);
throw new ApplicationException(errorText);
}
}
/// <summary>
/// Generate a stack trace to display/log with the assertion text.
/// <remarks>
/// The trace information includes file and line number information
/// if its available, as well as a copy of the line of text if
/// the source code is available. This function is only included in
/// DEBUG builds of the application.
/// </remarks>
/// <param name="lineNumber">
/// The line of the current error in the function. This
/// value should be retrieved by call Application.LineNumber
/// in the parameter list of any of the Check* functions. If
/// LineNumber is not provided,then the next executable line is used.
/// </param>
/// </summary>
[ConditionalAttribute("DEBUG")]
private static void GenerateStackTrace(int lineNumber)
{
currentTrace = String.Empty;
// #if DEBUG
StringBuilder message; //Used for smart string concatenation
String fileName; //The source file name
int currentLine; //The line to process in the source file
String sourceLine; //The line from the source file
StreamReader fileStream = null; //The reader used to scan the source file
bool openedFile = false;
StackTrace curTrace;
StackFrame curFrame;
message = new StringBuilder();
//New StackTrace should never fail, but Try/Catch to be rock solid.
try
{
//Get a new stack trace with line information. Skip the first function
// and second functions (this one, and the calling Check* function)
curTrace = new StackTrace(2, true);
try
{
//
// Get the first retrieved stack frame and attempt to get
// file information from the trace, then open the file
// and find the specified line. Display as much information
// as possible if this is not supported.
//
curFrame = curTrace.GetFrame(0);
//Retrieve and add File/Line information. Note that we only
//proceed if both of these are available.
if ((String.Empty != (fileName = curFrame.GetFileName())) &&
(0 <= (currentLine = (lineNumber != 0) ? lineNumber : curFrame.GetFileLineNumber())))
{
//Append File name and line number
message.Append(fileName).Append(", Line: ").Append(currentLine);
//Append the actual code if we can find the source file
fileStream = new StreamReader(fileName);
openedFile = true;
do
{
sourceLine = fileStream.ReadLine();
--currentLine;
} while (currentLine != 0);
message.Append("\r\n");
if (lineNumber != 0)
{
message.Append("Current executable line:");
}
else
{
message.Append("\r\n").Append("Next executable line:");
}
message.Append("\r\n").Append(sourceLine.Trim());
}
}
catch
{
//Ignore errors, just show as much as we can
}
finally
{
//Always close the file
if (openedFile) fileStream.Close();
}
//Retrieve the final string
currentTrace = message.ToString();
}
catch
{
//Nothing to do, just get out of here with the default (empty) return value
}
// #endif
}
} // class ApplicationAssert
} // namespace Duwamish7.SystemFramework

View File

@@ -0,0 +1,219 @@
using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
namespace SystemFramework
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
public class ApplicationLog
{
//This object is added as a debug listener.
private static StreamWriter debugWriter;
/// <summary>
///
/// </summary>
/// <param name="ex"></param>
/// <param name="catchInfo"></param>
/// <returns></returns>
public static String FormatException(Exception ex, String catchInfo)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.Append("Message:"+DateTime.Now.ToString()+"\r\n");
if (catchInfo != String.Empty)
{
strBuilder.Append(catchInfo).Append("\r\n");
}
strBuilder.Append(ex.Message).Append("\r\n").Append(ex.StackTrace).Append("\r\n").Append(ex.Source).Append("\r\n").Append(DateTime.Now.ToString()).Append("\r\n\r\n");
ApplicationAssert.GetDetailMessage(strBuilder.ToString(), ApplicationAssert.LineNumber);
return strBuilder.ToString();
}
/// <summary>
///
/// </summary>
/// <param name="ex"></param>
/// <param name="catchInfo"></param>
public static void WriteLog( Exception ex, String catchInfo)
{
WriteLog(FormatException( ex, catchInfo));
}
/// <summary>
/// Determine where a string needs to be written based on the
/// configuration settings and the error level.
/// <param name="messageText">The string to be logged.</param>
/// </summary>
public static void WriteLog( String messageText)
{
//
// Be very careful by putting a Try/Catch around the entire routine.
// We should never throw an exception while logging.
//
try
{
//
// Write the message to the trace file
//
//Make sure a tracing file is specified.
if (debugWriter != null)
{
lock(debugWriter)
{
//写入Log文件
Debug.WriteLine("Time:"+DateTime.Now.ToString()+"\r\n"+messageText);
debugWriter.Flush();
//显示到屏幕
// Trace.WriteLine(messageText);
// Trace.Flush();
}
}
}
catch {} //Ignore any exceptions.
}
static ApplicationLog()
{
//Protect thread locks with Try/Catch to guarantee that we let go of the lock.
try
{
//See if there is a debug configuration file specified and set up the
// tracing variables.
bool clearSettings = true;
try
{
String tracingFile = System.Configuration.ConfigurationManager.AppSettings.GetValues("TracingTraceFile")[0].ToString();
FileInfo file = new FileInfo(tracingFile);
debugWriter = new StreamWriter(file.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite));
Debug.Listeners.Add(new TextWriterTraceListener(debugWriter));
TextWriterTraceListener consoleWriter = new
TextWriterTraceListener(System.Console.Out);
Trace.Listeners.Add(consoleWriter);
clearSettings = false;
}
catch
{
//Ignore the error
}
//Use default (empty) values if something went wrong
if (clearSettings)
{
debugWriter = null;
}
}
finally
{
//Remove the lock from the class object
//Monitor.Exit(myType);
}
}
private static void GenerateStackTrace(int lineNumber, out String currentTrace)
{
currentTrace = String.Empty;
StringBuilder message; //Used for smart string concatenation
String fileName; //The source file name
int currentLine; //The line to process in the source file
String sourceLine; //The line from the source file
StreamReader fileStream = null; //The reader used to scan the source file
bool openedFile = false;
StackTrace curTrace;
StackFrame curFrame;
message = new StringBuilder();
//New StackTrace should never fail, but Try/Catch to be rock solid.
try
{
//Get a new stack trace with line information. Skip the first function
// and second functions (this one, and the calling Check* function)
curTrace = new StackTrace(2, true);
try
{
//
// Get the first retrieved stack frame and attempt to get
// file information from the trace, then open the file
// and find the specified line. Display as much information
// as possible if this is not supported.
//
curFrame = curTrace.GetFrame(0);
//Retrieve and add File/Line information. Note that we only
//proceed if both of these are available.
if ((String.Empty != (fileName = curFrame.GetFileName())) &&
(0 <= (currentLine = (lineNumber != 0) ? lineNumber : curFrame.GetFileLineNumber())))
{
//Append File name and line number
message.Append(fileName).Append(", Line: ").Append(currentLine);
//Append the actual code if we can find the source file
fileStream = new StreamReader(fileName);
openedFile = true;
do
{
sourceLine = fileStream.ReadLine();
--currentLine;
} while (currentLine != 0);
message.Append("\r\n");
if (lineNumber != 0)
{
message.Append("Current executable line:");
}
else
{
message.Append("\r\n").Append("Next executable line:");
}
message.Append("\r\n").Append(sourceLine.Trim());
}
}
catch
{
//Ignore errors, just show as much as we can
}
finally
{
//Always close the file
if (openedFile) fileStream.Close();
}
//Retrieve the final string
currentTrace = message.ToString();
}
catch
{
//Nothing to do, just get out of here with the default (empty) return value
}
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Web;
using System.ComponentModel;
namespace DataLinkMesWork
{
/// <summary>
///
/// </summary>
public class Reflection
{
/// <summary>
/// 通过反射调用动态连接库方法
/// </summary>
/// <param name="assembly">程序集(DLL)</param>
/// <param name="Namespace">命名空间名称,如果无命名空间写“”</param>
/// <param name="Classname">类名</param>
/// <param name="Method">方法名</param>
/// <param name="Paramsters">函数参数数组</param>
static public string ReflectionMethod(Assembly assembly, string Namespace, string Classname, string Method, object[] Paramsters)
{
try
{
Type t;
//类
if (Namespace == "")
{
t = assembly.GetType(Classname);
}
else
{
t = assembly.GetType(Namespace + "." + Classname);
}
if (t == null)
{
return "类不存在";
}
////方法
MethodInfo mi = t.GetMethod(Method); //获取方法
if (mi == null)
{
return "方法不存在";
}
ParameterInfo[] paramsInfo = mi.GetParameters();
for (int i = 0; i < paramsInfo.Length; i++)
{
Type tType = paramsInfo[i].ParameterType;
//改变参数类型
Paramsters[i]= SD_ChanageType(Paramsters[i],tType);
}
/// 建一个实例化的类
object instanceObject = Activator.CreateInstance(t);
mi.Invoke(instanceObject, Paramsters);
return "ok";
}
catch(Exception err)
{
return err.ToString();
}
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <param name="convertsionType"></param>
/// <returns></returns>
public static object SD_ChanageType(object value, Type convertsionType)
{
//判断convertsionType类型是否为泛型因为nullable是泛型类,
if (convertsionType.IsGenericType &&
//判断convertsionType是否为nullable泛型类
convertsionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
if (value == null || value.ToString().Length == 0)
{
return null;
}
//如果convertsionType为nullable类声明一个NullableConverter类该类提供从Nullable类到基础基元类型的转换
NullableConverter nullableConverter = new NullableConverter(convertsionType);
//将convertsionType转换为nullable对的基础基元类型
convertsionType = nullableConverter.UnderlyingType;
}
return Convert.ChangeType(value, convertsionType);
}
}
}

View File

@@ -0,0 +1,240 @@
using System;
using System.Collections.Generic;
using System.Text;
//添加的命名空间引用
using System.Net;
using System.Net.Sockets;
using System.Threading;
using SystemFramework;
namespace DataLinkMesWork
{
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void DelegateClassHandle_UDPServerReceive(object sender, UDPServerReceiveEvetnArgs e);
/// <summary>
///
/// </summary>
public class UDPServerReceiveEvetnArgs : EventArgs
{
object tagKey = 0;
object opName = "";
object tagValue = 0;
object tagQuality = 0;
/// <summary>
///
/// </summary>
public UDPServerReceiveEvetnArgs()
{ }
/// <summary>
///
/// </summary>
public object TagKey
{
get { return this.tagKey; }
set { this.tagKey = value; }
}
/// <summary>
///
/// </summary>
public object TagValue
{
get { return this.tagValue; }
set { this.tagValue = value; }
}
/// <summary>
///
/// </summary>
public object OpName
{
get { return this.opName; }
set { this.opName = value; }
}
/// <summary>
///
/// </summary>
public object TagQuality
{
get { return this.tagQuality; }
set { this.tagQuality = value; }
}
}
/// <summary>
///
/// </summary>
public class TagDataUDPServerReceive
{
private object tagKey;
/// <summary>
///
/// </summary>
public object TagKey
{
get { return tagKey; }
set { tagKey = value; }
}
private object tagValue;
/// <summary>
///
/// </summary>
public object TagValue
{
get { return tagValue; }
set { tagValue = value; }
}
/// <summary>
///
/// </summary>
public event DelegateClassHandle_UDPServerReceive TagDataOnChange;
/// <summary>
///
/// </summary>
/// <param name="e"></param>
public void InvokeTagData(UDPServerReceiveEvetnArgs e)
{
if (TagDataOnChange != null)
{
TagDataOnChange(this, e);
}
}
/// <summary>
///
/// </summary>
/// <param name="tagValue"></param>
public void InvokeTagData(object tagValue)
{
if (TagDataOnChange != null)
{
UDPServerReceiveEvetnArgs e = new UDPServerReceiveEvetnArgs();
e.TagValue = tagValue;
TagDataOnChange(this, e);
}
}
}
/// <summary>
/// 使用的接收端口
/// </summary>
public class UDPServerReceive
{
Thread mythread;
//使用的接收端口
/// <summary>
/// 端口号
/// </summary>
private int port;
/// <summary>
/// udp连接对象
/// </summary>
private UdpClient udpclient;
/// <summary>
///
/// </summary>
public TagDataUDPServerReceive m_TagDataUDPServerReceive;
/// <summary>
///
/// </summary>
/// <param name="port_In"></param>
public UDPServerReceive(int port_In)
{
m_TagDataUDPServerReceive = new TagDataUDPServerReceive();
port = port_In;
//创建一个线程接收接收远程主机发来的信息
mythread = new Thread(new ThreadStart(RecData));
//将线程设为后台运行
mythread.IsBackground = true;
mythread.Start();
}
/// <summary>
/// 开始接受新的端口数据
/// </summary>
/// <param name="port_In"></param>
public void Start(int port_In)
{
port = port_In;
if (mythread != null)
{
try
{
if (udpclient != null)
{
udpclient.Close();
}
mythread.Abort();
}
catch { }
}
//创建一个线程接收接收远程主机发来的信息
mythread = new Thread(new ThreadStart(RecData));
//将线程设为后台运行
mythread.IsBackground = true;
mythread.Start();
}
/// <summary>
/// 开始接受新的端口数据
/// </summary>
/// <param name="port_In"></param>
public void Close(int port_In)
{
port = port_In;
if (mythread != null)
{
try
{
if (udpclient != null)
{
udpclient.Close();
}
//mythread.Abort();
}
catch { }
}
}
/// <summary>
/// 在后台运行的接收线程
/// </summary>
private void RecData()
{
try
{
//本机指定端口接收
udpclient = new UdpClient(port);
}
catch (Exception err)
{
ApplicationLog.WriteLog(err, "RecData:new UdpClient(port)=" + port.ToString());
}
IPEndPoint remote = null;
while (true)
{
try
{
//接收从远程主机发送过来的信息
string sendMessage;
//关闭udpclient时此句会产生异常
byte[] bytes = udpclient.Receive(ref remote);
string strSource = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
sendMessage = strSource;
m_TagDataUDPServerReceive.InvokeTagData(sendMessage);
}
catch
{
break;
}
}
}
}
}

View File

@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Text;
//添加的命名空间引用
using System.Net;
using System.Net.Sockets;
using System.Threading;
using SystemFramework;
namespace DataLinkMesWork
{
/// <summary>
/// 利用UDP向端口port发送数据
/// </summary>
public class UDPClientSend
{
/// <summary>
/// 向指定IP发送UDP
/// </summary>
/// <param name="ip"></param>
/// <param name="port"></param>
/// <param name="text"></param>
public static void Send(string ip, int port, string text)
{
UdpClient myUdpclient = new UdpClient();
string sendMessage;
try
{
DateTime m_DateTime = DateTime.Now;
IPEndPoint iep;
if (ip == "")
{
iep = new IPEndPoint(IPAddress.Broadcast, port);
}
else
{
iep = new IPEndPoint(IPAddress.Parse(ip), port);
}
sendMessage = text;
byte[] bytes;
bytes = System.Text.Encoding.UTF8.GetBytes(sendMessage);
myUdpclient.Send(bytes, bytes.Length, iep);
myUdpclient.Close();
}
catch (Exception err)
{
ApplicationLog.WriteLog(err, "UDPClientSend Send");
}
finally
{
myUdpclient.Close();
}
}
/// <summary>
/// 利用UDP向端口port发送数据
/// </summary>
/// <param name="port"></param>
/// <param name="opName"></param>
/// <param name="text"></param>
public static void Send(int port, string text)
{
UdpClient myUdpclient = new UdpClient();
string sendMessage;
try
{
DateTime m_DateTime = DateTime.Now;
IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, port);
sendMessage = text;
byte[] bytes;
bytes = System.Text.Encoding.UTF8.GetBytes(sendMessage);
myUdpclient.Send(bytes, bytes.Length, iep);
myUdpclient.Close();
}
catch (Exception err)
{
ApplicationLog.WriteLog(err, "UDPClientSend Send");
}
finally
{
myUdpclient.Close();
}
}
/// <summary>
/// 向指定端口发生UDP广播
/// </summary>
/// <param name="port"></param>
/// <param name="text"></param>
public static void Send_ALL(int port, string text)
{
UdpClient myUdpclient = new UdpClient();
string sendMessage;
try
{
DateTime m_DateTime = DateTime.Now;
IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, port);
sendMessage = text;
byte[] bytes;
bytes = System.Text.Encoding.UTF8.GetBytes(sendMessage);
myUdpclient.Send(bytes, bytes.Length, iep);
myUdpclient.Close();
}
catch (Exception err)
{
ApplicationLog.WriteLog(err, "UDPClientSend Send");
}
finally
{
myUdpclient.Close();
}
}
/// <summary>
/// 利用UDP向端口port发送数据
/// </summary>
/// <param name="port"></param>
/// <param name="opName"></param>
/// <param name="text"></param>
public static void Send(int port,string opName, string text)
{
UdpClient myUdpclient = new UdpClient();
if (opName == null)
opName = "OpName";
string sendMessage;
try
{
DateTime m_DateTime = DateTime.Now;
IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, port);
sendMessage = text;
byte[] bytes;
bytes = System.Text.Encoding.UTF8.GetBytes(sendMessage);
myUdpclient.Send(bytes, bytes.Length, iep);
myUdpclient.Close();
if (ApplicationConfig.IsUsingUdpMonitor == 1)
{
if (port != ApplicationConfig.DupPort_Recieve_Monitor)
{
sendMessage = "Send?" + ApplicationConfig.IPAddress + "?" + port.ToString() + "?" + opName+"?" + text + "?" + m_DateTime.ToString("G") + "." + m_DateTime.Millisecond.ToString();
UdpClient myUdpclientMonitor = new UdpClient();
iep = new IPEndPoint(IPAddress.Broadcast, ApplicationConfig.DupPort_Recieve_Monitor);
bytes = System.Text.Encoding.UTF8.GetBytes(sendMessage);
myUdpclientMonitor.Send(bytes, bytes.Length, iep);
myUdpclientMonitor.Close();
}
}
}
catch (Exception err)
{
ApplicationLog.WriteLog(err, "UDPClientSend Send");
}
finally
{
myUdpclient.Close();
}
}
/// <summary>
/// 利用监控端口41199发送数据
/// </summary>
/// <param name="ip"></param>
/// <param name="port"></param>
/// <param name="opName"></param>
/// <param name="text"></param>
public static void SendToMonitor(string ip,int port, string opName, string text)
{
UdpClient myUdpclient = new UdpClient();
string sendMessage;
try
{
DateTime m_DateTime = DateTime.Now;
IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, ApplicationConfig.DupPort_Recieve_Monitor);
sendMessage = "Receive?" + ip + "?" + port.ToString() + "?" + opName + "?" + text + "?" + m_DateTime.ToString("G") + "." + m_DateTime.Millisecond.ToString();
byte[] bytes;
bytes = System.Text.Encoding.UTF8.GetBytes(sendMessage);
myUdpclient.Send(bytes, bytes.Length, iep);
myUdpclient.Close();
}
catch (Exception err)
{
ApplicationLog.WriteLog(err, "UDPClientSend Send");
}
finally
{
myUdpclient.Close();
}
}
}
}