##// END OF EJS Templates
Fixed Safe.DisposeCollection NullReferenceException...
cin -
r292:807f0bc35f40 v3
parent child
Show More
@@ -1,36 +1,36
1 <Project Sdk="Microsoft.NET.Sdk">
1 <Project Sdk="Microsoft.NET.Sdk">
2
2
3 <PropertyGroup>
3 <PropertyGroup>
4 <Authors>Sergey Smirnov</Authors>
4 <Authors>Sergey Smirnov</Authors>
5 <Title>Implab.ServiceHost</Title>
5 <Title>Implab.ServiceHost</Title>
6 <Description>The configurable application host.
6 <Description>The configurable application host.
7 Provides simple and flexible Xml configuration for UnityContainer.
7 Provides simple and flexible Xml configuration for UnityContainer.
8 </Description>
8 </Description>
9 <Copyright>2012-2018 Sergey Smirnov</Copyright>
9 <Copyright>2012-2018 Sergey Smirnov</Copyright>
10 <Version>1.0.1</Version>
10 <Version>1.0.3</Version>
11 <PackageLicenseUrl>https://bitbucket.org/wozard/implabnet/src/v3/Implab/license.txt</PackageLicenseUrl>
11 <PackageLicenseUrl>https://bitbucket.org/wozard/implabnet/src/v3/Implab/license.txt</PackageLicenseUrl>
12 <PackageProjectUrl>https://bitbucket.org/wozard/implabnet</PackageProjectUrl>
12 <PackageProjectUrl>https://bitbucket.org/wozard/implabnet</PackageProjectUrl>
13 <RepositoryUrl>https://bitbucket.org/wozard/implabnet</RepositoryUrl>
13 <RepositoryUrl>https://bitbucket.org/wozard/implabnet</RepositoryUrl>
14 <RepositoryType>mercurial</RepositoryType>
14 <RepositoryType>mercurial</RepositoryType>
15 <PackageTags>Implab;Xml configuration;IoC;Unity container</PackageTags>
15 <PackageTags>Implab;Xml configuration;IoC;Unity container</PackageTags>
16 </PropertyGroup>
16 </PropertyGroup>
17
17
18
18
19 <PropertyGroup Condition="'$(OSTYPE)'=='linux'">
19 <PropertyGroup Condition="'$(OSTYPE)'=='linux'">
20 <TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
20 <TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
21 <FrameworkPathOverride Condition="'$(TargetFramework)'=='net46'">/usr/lib/mono/4.5/</FrameworkPathOverride>
21 <FrameworkPathOverride Condition="'$(TargetFramework)'=='net46'">/usr/lib/mono/4.5/</FrameworkPathOverride>
22 </PropertyGroup>
22 </PropertyGroup>
23
23
24 <PropertyGroup Condition="'$(OSTYPE)'=='windows'">
24 <PropertyGroup Condition="'$(OSTYPE)'=='windows'">
25 <TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
25 <TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
26 </PropertyGroup>
26 </PropertyGroup>
27
27
28 <ItemGroup>
28 <ItemGroup>
29 <PackageReference Include="Unity" Version="5.8.6" />
29 <PackageReference Include="Unity" Version="5.8.6" />
30 </ItemGroup>
30 </ItemGroup>
31
31
32 <ItemGroup>
32 <ItemGroup>
33 <ProjectReference Include="..\Implab\Implab.csproj" />
33 <ProjectReference Include="..\Implab\Implab.csproj" />
34 </ItemGroup>
34 </ItemGroup>
35
35
36 </Project>
36 </Project>
@@ -1,156 +1,161
1 using System;
1 using System;
2 using System.IO;
2 using System.IO;
3 using System.Reflection;
3 using System.Reflection;
4 using Implab.Diagnostics;
4 using Implab.Diagnostics;
5 using Unity;
5 using Unity;
6
6
7 namespace Implab.ServiceHost.Unity {
7 namespace Implab.ServiceHost.Unity {
8 using Log = Trace<ContainerBuilder>;
8 using Log = Trace<ContainerBuilder>;
9
9
10 public class ContainerBuilder {
10 public class ContainerBuilder {
11
11
12 readonly TypeResolver m_resolver;
12 readonly TypeResolver m_resolver;
13
13
14 readonly IUnityContainer m_container;
14 readonly IUnityContainer m_container;
15
15
16 readonly ContainerConfigurationSchema m_schema;
16 readonly ContainerConfigurationSchema m_schema;
17
17
18 Uri m_location;
18 Uri m_location;
19
19
20 public IUnityContainer Container {
20 public IUnityContainer Container {
21 get {
21 get {
22 return m_container;
22 return m_container;
23 }
23 }
24 }
24 }
25
25
26 public ContainerBuilder() : this(null, null) {
26 public ContainerBuilder() : this(null, null) {
27 }
27 }
28
28
29 public ContainerBuilder(IUnityContainer container, ContainerConfigurationSchema schema) {
29 public ContainerBuilder(IUnityContainer container, ContainerConfigurationSchema schema) {
30 m_container = container ?? new UnityContainer();
30 m_container = container ?? new UnityContainer();
31 m_resolver = new TypeResolver();
31 m_resolver = new TypeResolver();
32 m_schema = schema ?? ContainerConfigurationSchema.Default;
32 m_schema = schema ?? ContainerConfigurationSchema.Default;
33 }
33 }
34
34
35 public Type ResolveType(string typeReference) {
35 public Type ResolveType(string typeReference) {
36 var resolved = string.IsNullOrEmpty(typeReference) ? null : m_resolver.Resolve(typeReference, true);
36 var resolved = string.IsNullOrEmpty(typeReference) ? null : m_resolver.Resolve(typeReference, true);
37 Log.Debug("ResolveType('{0}'): {1}", typeReference, resolved?.FullName);
37 Log.Debug("ResolveType('{0}'): {1}", typeReference, resolved?.FullName);
38 return resolved;
38 return resolved;
39 }
39 }
40
40
41 public void Visit(ITypeRegistration registration) {
41 public void Visit(ITypeRegistration registration) {
42 Safe.ArgumentNotNull(registration, nameof(registration));
42 Safe.ArgumentNotNull(registration, nameof(registration));
43
43
44 var registrationType = registration.GetRegistrationType(this);
44 var registrationType = registration.GetRegistrationType(this);
45 var implementationType = registration.GetImplementationType(this) ?? registrationType;
45 var implementationType = registration.GetImplementationType(this) ?? registrationType;
46
46
47 if (registrationType == null)
47 if (registrationType == null)
48 throw new Exception($"A type must be specified for the registration {registration.Name}");
48 throw new Exception($"A type must be specified for the registration {registration.Name}");
49
49
50 var builder = new TypeRegistrationBuilder(
50 var builder = new TypeRegistrationBuilder(
51 m_resolver,
51 m_resolver,
52 registrationType,
52 registrationType,
53 implementationType,
53 implementationType,
54 this
54 this
55 );
55 );
56
56
57 builder.Lifetime = registration.GetLifetime(this);
57 builder.Lifetime = registration.GetLifetime(this);
58
58
59 if (registration.MemberInjections != null) {
59 if (registration.MemberInjections != null) {
60 foreach(var member in registration.MemberInjections)
60 foreach(var member in registration.MemberInjections)
61 member.Visit(builder);
61 member.Visit(builder);
62 }
62 }
63
63
64 m_container.RegisterType(
64 m_container.RegisterType(
65 builder.RegistrationType,
65 builder.RegistrationType,
66 builder.ImplementationType,
66 builder.ImplementationType,
67 registration.Name,
67 registration.Name,
68 builder.Lifetime,
68 builder.Lifetime,
69 builder.Injections
69 builder.Injections
70 );
70 );
71 }
71 }
72
72
73 public void Visit(IInstanceRegistration registration) {
73 public void Visit(IInstanceRegistration registration) {
74 Safe.ArgumentNotNull(registration, nameof(registration));
74 Safe.ArgumentNotNull(registration, nameof(registration));
75
75
76 var registrationType = registration.GetRegistrationType(this);
76 var registrationType = registration.GetRegistrationType(this);
77
77
78 var builder = new InstanceRegistrationBuilder (
78 var builder = new InstanceRegistrationBuilder (
79 m_resolver,
79 m_resolver,
80 registrationType,
80 registrationType,
81 this
81 this
82 );
82 );
83
83
84 builder.Lifetime = registration.GetLifetime(this);
84 builder.Lifetime = registration.GetLifetime(this);
85
85
86 if (registration.MemberInjections != null) {
86 if (registration.MemberInjections != null) {
87 foreach(var member in registration.MemberInjections)
87 foreach(var member in registration.MemberInjections)
88 member.Visit(builder.ValueBuilder);
88 member.Visit(builder.ValueBuilder);
89 }
89 }
90
90
91 if (builder.RegistrationType == null && builder.ValueBuilder.ValueType == null)
91 if (builder.RegistrationType == null && builder.ValueBuilder.ValueType == null)
92 throw new Exception($"A type must be specified for the registration {registration.Name}");
92 throw new Exception($"A type must be specified for the registration {registration.Name}");
93
93
94 m_container.RegisterInstance(
94 m_container.RegisterInstance(
95 builder.RegistrationType ?? builder.ValueBuilder.ValueType,
95 builder.RegistrationType ?? builder.ValueBuilder.ValueType,
96 registration.Name,
96 registration.Name,
97 builder.ValueBuilder.Value,
97 builder.ValueBuilder.Value,
98 builder.Lifetime
98 builder.Lifetime
99 );
99 );
100 }
100 }
101
101
102 public void AddNamespace(string ns) {
102 public void AddNamespace(string ns) {
103 m_resolver.AddNamespace(ns);
103 m_resolver.AddNamespace(ns);
104 Log.Log($"AddNamespace: {ns}");
104 }
105 }
105
106
106 public void AddAssembly(string assembly) {
107 public void AddAssembly(string assembly) {
107
108 var asm = Assembly.Load(assembly);
109 Log.Log($"AddAssembly: {assembly} -> {asm.FullName}");
108 }
110 }
109
111
110 /// <summary>
112 /// <summary>
111 /// Includes the confguration. Creates a new <see cref="ContainerBuilder"/>,
113 /// Includes the confguration. Creates a new <see cref="ContainerBuilder"/>,
112 /// and loads the configuration to it. The created builder will share the
114 /// and loads the configuration to it. The created builder will share the
113 /// container and will have its own isolated type resolver.
115 /// container and will have its own isolated type resolver.
114 /// </summary>
116 /// </summary>
115 /// <param name="file">A path to configuration relative to the current configuration.</param>
117 /// <param name="file">A path to configuration relative to the current configuration.</param>
116 public void Include(string file) {
118 public void Include(string file) {
117 var includeContext = new ContainerBuilder(m_container, m_schema);
119 var includeContext = new ContainerBuilder(m_container, m_schema);
118
120
119 if (m_location != null) {
121 if (m_location != null) {
120 var uri = new Uri(m_location, file);
122 var uri = new Uri(m_location, file);
121 includeContext.LoadConfig(uri);
123 includeContext.LoadConfig(uri);
122 } else {
124 } else {
123 includeContext.LoadConfig(file);
125 includeContext.LoadConfig(file);
124 }
126 }
125 }
127 }
126
128
127 /// <summary>
129 /// <summary>
128 /// Resolves a path ralatively to the current container configuration location.
130 /// Resolves a path ralatively to the current container configuration location.
129 /// </summary>
131 /// </summary>
130 /// <param name="location">A path yto resolve</param>
132 /// <param name="location">A path yto resolve</param>
131 /// <returns>Resolved Uri fot the specified location</returns>
133 /// <returns>Resolved Uri fot the specified location</returns>
132 public Uri MakeLocationUri(string location) {
134 public Uri MakeLocationUri(string location) {
133 return m_location != null ? new Uri(m_location, location) : new Uri(location);
135 return m_location != null ? new Uri(m_location, location) : new Uri(location);
134 }
136 }
135
137
136 /// <summary>
138 /// <summary>
137 /// Loads a configuration from the specified local file.
139 /// Loads a configuration from the specified local file.
138 /// </summary>
140 /// </summary>
139 /// <param name="file">The path to the configuration file.</param>
141 /// <param name="file">The path to the configuration file.</param>
140 public void LoadConfig(string file) {
142 public void LoadConfig(string file) {
141 Safe.ArgumentNotEmpty(file, nameof(file));
143 Safe.ArgumentNotEmpty(file, nameof(file));
142
144
143 LoadConfig(new Uri(Path.GetFullPath(file)));
145 LoadConfig(new Uri(Path.GetFullPath(file)));
144 }
146 }
145
147
146 public void LoadConfig(Uri location) {
148 public void LoadConfig(Uri location) {
147 Safe.ArgumentNotNull(location, nameof(location));
149 Safe.ArgumentNotNull(location, nameof(location));
148
150
151 Log.Log($"LoadConfig {location}");
152 Safe.ArgumentNotNull(location, nameof(location));
153
149 m_location = location;
154 m_location = location;
150
155
151 var config = m_schema.LoadConfig(location.ToString());
156 var config = m_schema.LoadConfig(location.ToString());
152 config.Visit(this);
157 config.Visit(this);
153 }
158 }
154
159
155 }
160 }
156 } No newline at end of file
161 }
@@ -1,189 +1,193
1 using System;
1 using System;
2 using System.Collections.Generic;
2 using System.Collections.Generic;
3 using System.Linq;
3 using System.Linq;
4 using System.Text;
4 using System.Text;
5 using System.Text.RegularExpressions;
5 using System.Text.RegularExpressions;
6 using System.Diagnostics;
6 using System.Diagnostics;
7 using System.Collections;
7 using System.Collections;
8 using System.Runtime.CompilerServices;
8 using System.Runtime.CompilerServices;
9 using System.Threading.Tasks;
9 using System.Threading.Tasks;
10 using System.Threading;
10 using System.Threading;
11
11
12 #if NET_4_5
12 #if NET_4_5
13 using System.Threading.Tasks;
13 using System.Threading.Tasks;
14 #endif
14 #endif
15
15
16 namespace Implab
16 namespace Implab
17 {
17 {
18 public static class Safe
18 public static class Safe
19 {
19 {
20 [MethodImpl(MethodImplOptions.AggressiveInlining)]
20 [MethodImpl(MethodImplOptions.AggressiveInlining)]
21 public static void ArgumentAssert(bool condition, string paramName) {
21 public static void ArgumentAssert(bool condition, string paramName) {
22 if (!condition)
22 if (!condition)
23 throw new ArgumentException("The parameter is invalid", paramName);
23 throw new ArgumentException("The parameter is invalid", paramName);
24 }
24 }
25
25
26 [MethodImpl(MethodImplOptions.AggressiveInlining)]
26 [MethodImpl(MethodImplOptions.AggressiveInlining)]
27 public static void ArgumentMatch(string value, string paramName, Regex rx) {
27 public static void ArgumentMatch(string value, string paramName, Regex rx) {
28 if (rx == null)
28 if (rx == null)
29 throw new ArgumentNullException("rx");
29 throw new ArgumentNullException("rx");
30 if (!rx.IsMatch(value))
30 if (!rx.IsMatch(value))
31 throw new ArgumentException(String.Format("The prameter value must match {0}", rx), paramName);
31 throw new ArgumentException(String.Format("The prameter value must match {0}", rx), paramName);
32 }
32 }
33
33
34 [MethodImpl(MethodImplOptions.AggressiveInlining)]
34 [MethodImpl(MethodImplOptions.AggressiveInlining)]
35 public static void ArgumentNotEmpty(string value, string paramName) {
35 public static void ArgumentNotEmpty(string value, string paramName) {
36 if (String.IsNullOrEmpty(value))
36 if (String.IsNullOrEmpty(value))
37 throw new ArgumentException("The parameter can't be empty", paramName);
37 throw new ArgumentException("The parameter can't be empty", paramName);
38 }
38 }
39
39
40 [MethodImpl(MethodImplOptions.AggressiveInlining)]
40 [MethodImpl(MethodImplOptions.AggressiveInlining)]
41 public static void ArgumentNotEmpty<T>(T[] value, string paramName) {
41 public static void ArgumentNotEmpty<T>(T[] value, string paramName) {
42 if (value == null || value.Length == 0)
42 if (value == null || value.Length == 0)
43 throw new ArgumentException("The array must be not emty", paramName);
43 throw new ArgumentException("The array must be not emty", paramName);
44 }
44 }
45
45
46 [MethodImpl(MethodImplOptions.AggressiveInlining)]
46 [MethodImpl(MethodImplOptions.AggressiveInlining)]
47 public static void ArgumentNotNull(object value, string paramName) {
47 public static void ArgumentNotNull(object value, string paramName) {
48 if (value == null)
48 if (value == null)
49 throw new ArgumentNullException(paramName);
49 throw new ArgumentNullException(paramName);
50 }
50 }
51
51
52 [MethodImpl(MethodImplOptions.AggressiveInlining)]
52 [MethodImpl(MethodImplOptions.AggressiveInlining)]
53 internal static void ArgumentGreaterEqThan(int value, int min, string paramName) {
53 internal static void ArgumentGreaterEqThan(int value, int min, string paramName) {
54 if (value < min)
54 if (value < min)
55 throw new ArgumentOutOfRangeException(paramName);
55 throw new ArgumentOutOfRangeException(paramName);
56 }
56 }
57
57
58 public static object CreateDefaultValue(Type type) {
58 public static object CreateDefaultValue(Type type) {
59 if (type.IsValueType)
59 if (type.IsValueType)
60 return Activator.CreateInstance(type);
60 return Activator.CreateInstance(type);
61
61
62 return null;
62 return null;
63 }
63 }
64
64
65 [MethodImpl(MethodImplOptions.AggressiveInlining)]
65 [MethodImpl(MethodImplOptions.AggressiveInlining)]
66 public static void ArgumentInRange(bool condition, string paramName) {
66 public static void ArgumentInRange(bool condition, string paramName) {
67 if (!condition)
67 if (!condition)
68 throw new ArgumentOutOfRangeException(paramName);
68 throw new ArgumentOutOfRangeException(paramName);
69 }
69 }
70
70
71 [MethodImpl(MethodImplOptions.AggressiveInlining)]
71 [MethodImpl(MethodImplOptions.AggressiveInlining)]
72 public static void ArgumentOfType(object value, Type type, string paramName) {
72 public static void ArgumentOfType(object value, Type type, string paramName) {
73 if (!type.IsInstanceOfType(value))
73 if (!type.IsInstanceOfType(value))
74 throw new ArgumentException(String.Format("The parameter must be of type {0}", type), paramName);
74 throw new ArgumentException(String.Format("The parameter must be of type {0}", type), paramName);
75 }
75 }
76
76
77 public static void Dispose(params IDisposable[] objects) {
77 public static void Dispose(params IDisposable[] objects) {
78 if (objects != null)
78 foreach (var d in objects)
79 foreach (var d in objects)
79 if (d != null)
80 if (d != null)
80 d.Dispose();
81 d.Dispose();
81 }
82 }
82
83
83 public static void Dispose(params object[] objects) {
84 public static void Dispose(params object[] objects) {
85 if (objects != null)
84 foreach (var obj in objects) {
86 foreach (var obj in objects) {
85 var d = obj as IDisposable;
87 var d = obj as IDisposable;
86 if (d != null)
88 if (d != null)
87 d.Dispose();
89 d.Dispose();
88 }
90 }
89 }
91 }
90
92
91 public static void DisposeCollection(IEnumerable<IDisposable> objects) {
93 public static void DisposeCollection(IEnumerable<IDisposable> objects) {
94 if (objects != null)
92 foreach (var d in objects)
95 foreach (var d in objects)
93 Dispose(d);
96 Dispose(d);
94 }
97 }
95
98
96 public static void DisposeCollection(IEnumerable objects) {
99 public static void DisposeCollection(IEnumerable objects) {
100 if (objects != null)
97 foreach (var d in objects)
101 foreach (var d in objects)
98 Dispose(d);
102 Dispose(d);
99 }
103 }
100
104
101 public static void Dispose(object obj) {
105 public static void Dispose(object obj) {
102 if (obj is IDisposable)
106 if (obj is IDisposable)
103 Dispose((IDisposable)obj);
107 Dispose((IDisposable)obj);
104
108
105 }
109 }
106
110
107 [DebuggerStepThrough]
111 [DebuggerStepThrough]
108 public static void DispatchEvent<T>(this EventHandler<T> handler, object sender, T args) {
112 public static void DispatchEvent<T>(this EventHandler<T> handler, object sender, T args) {
109 if (handler != null)
113 if (handler != null)
110 handler(sender, args);
114 handler(sender, args);
111 }
115 }
112
116
113 [DebuggerStepThrough]
117 [DebuggerStepThrough]
114 public static void DispatchEvent(this EventHandler handler, object sender, EventArgs args) {
118 public static void DispatchEvent(this EventHandler handler, object sender, EventArgs args) {
115 if (handler != null)
119 if (handler != null)
116 handler(sender, args);
120 handler(sender, args);
117 }
121 }
118
122
119 [DebuggerStepThrough]
123 [DebuggerStepThrough]
120 public static IPromise<T> Run<T>(Func<T> action) {
124 public static IPromise<T> Run<T>(Func<T> action) {
121 ArgumentNotNull(action, "action");
125 ArgumentNotNull(action, "action");
122
126
123 try {
127 try {
124 return Promise.Resolve(action());
128 return Promise.Resolve(action());
125 } catch (Exception err) {
129 } catch (Exception err) {
126 return Promise.Reject<T>(err);
130 return Promise.Reject<T>(err);
127 }
131 }
128 }
132 }
129
133
130 [DebuggerStepThrough]
134 [DebuggerStepThrough]
131 public static IPromise Run(Action action) {
135 public static IPromise Run(Action action) {
132 ArgumentNotNull(action, "action");
136 ArgumentNotNull(action, "action");
133
137
134 try {
138 try {
135 action();
139 action();
136 return Promise.Resolve();
140 return Promise.Resolve();
137 } catch (Exception err) {
141 } catch (Exception err) {
138 return Promise.Reject(err);
142 return Promise.Reject(err);
139 }
143 }
140 }
144 }
141
145
142 [DebuggerStepThrough]
146 [DebuggerStepThrough]
143 public static IPromise Run(Func<IPromise> action) {
147 public static IPromise Run(Func<IPromise> action) {
144 ArgumentNotNull(action, "action");
148 ArgumentNotNull(action, "action");
145
149
146 try {
150 try {
147 return action() ?? Promise.Reject(new Exception("The action returned null"));
151 return action() ?? Promise.Reject(new Exception("The action returned null"));
148 } catch (Exception err) {
152 } catch (Exception err) {
149 return Promise.Reject(err);
153 return Promise.Reject(err);
150 }
154 }
151 }
155 }
152
156
153 public static void NoWait(IPromise promise) {
157 public static void NoWait(IPromise promise) {
154 }
158 }
155
159
156 public static void NoWait(Task promise) {
160 public static void NoWait(Task promise) {
157 }
161 }
158
162
159 public static void NoWait<T>(Task<T> promise) {
163 public static void NoWait<T>(Task<T> promise) {
160 }
164 }
161
165
162 public static void Noop() {
166 public static void Noop() {
163 }
167 }
164
168
165 public static void Noop(CancellationToken ct) {
169 public static void Noop(CancellationToken ct) {
166 ct.ThrowIfCancellationRequested();
170 ct.ThrowIfCancellationRequested();
167 }
171 }
168
172
169 public static Task CreateTask() {
173 public static Task CreateTask() {
170 return new Task(Noop);
174 return new Task(Noop);
171 }
175 }
172
176
173 public static Task CreateTask(CancellationToken ct) {
177 public static Task CreateTask(CancellationToken ct) {
174 return new Task(Noop, ct);
178 return new Task(Noop, ct);
175 }
179 }
176
180
177 [DebuggerStepThrough]
181 [DebuggerStepThrough]
178 public static IPromise<T> Run<T>(Func<IPromise<T>> action) {
182 public static IPromise<T> Run<T>(Func<IPromise<T>> action) {
179 ArgumentNotNull(action, "action");
183 ArgumentNotNull(action, "action");
180
184
181 try {
185 try {
182 return action() ?? Promise.Reject<T>(new Exception("The action returned null"));
186 return action() ?? Promise.Reject<T>(new Exception("The action returned null"));
183 } catch (Exception err) {
187 } catch (Exception err) {
184 return Promise.Reject<T>(err);
188 return Promise.Reject<T>(err);
185 }
189 }
186 }
190 }
187
191
188 }
192 }
189 }
193 }
General Comments 3
Under Review
author

Auto status change to "Under Review"

Approved
author

ok, latest stable version should be in default

You need to be logged in to leave comments. Login now