ikvm-fork/runtime/ClassLoaderWrapper.cs

1020 строки
29 KiB
C#
Исходник Обычный вид История

2002-12-18 19:00:25 +03:00
/*
2006-01-02 11:15:43 +03:00
Copyright (C) 2002, 2003, 2004, 2005, 2006 Jeroen Frijters
2002-12-18 19:00:25 +03:00
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Jeroen Frijters
jeroen@frijters.net
*/
using System;
using System.Reflection;
2005-12-07 12:06:32 +03:00
#if !COMPACT_FRAMEWORK
using System.Reflection.Emit;
#endif
2002-12-18 19:00:25 +03:00
using System.IO;
using System.Collections;
using System.Diagnostics;
2004-09-09 15:17:55 +04:00
using IKVM.Attributes;
using IKVM.Runtime;
2002-12-18 19:00:25 +03:00
2005-06-01 13:49:30 +04:00
namespace IKVM.Internal
2002-12-18 19:00:25 +03:00
{
2006-02-22 17:44:07 +03:00
abstract class ClassLoaderWrapper
2002-12-18 19:00:25 +03:00
{
2005-12-07 12:06:32 +03:00
private static readonly object wrapperLock = new object();
#if !COMPACT_FRAMEWORK
2006-02-22 17:44:07 +03:00
protected static bool arrayConstructionHack;
protected static readonly object arrayConstructionLock = new object();
2005-12-07 12:06:32 +03:00
#endif
2005-09-06 12:06:04 +04:00
private static readonly Hashtable typeToTypeWrapper = Hashtable.Synchronized(new Hashtable());
2005-06-01 13:49:30 +04:00
private static ClassLoaderWrapper bootstrapClassLoader;
2006-05-15 13:08:01 +04:00
#if WHIDBEY && !STATIC_COMPILER
private static readonly Hashtable reflectionOnlyClassLoaders = new Hashtable();
#endif
2006-04-10 13:09:09 +04:00
#if !STATIC_COMPILER
2005-06-01 13:49:30 +04:00
private static ClassLoaderWrapper systemClassLoader;
2006-04-10 13:09:09 +04:00
#endif
2005-06-01 13:49:30 +04:00
private object javaClassLoader;
2006-02-22 17:44:07 +03:00
protected Hashtable types = new Hashtable();
2005-06-01 13:49:30 +04:00
private ArrayList nativeLibraries;
private static Hashtable remappedTypes = new Hashtable();
2002-12-18 19:00:25 +03:00
2005-06-01 13:49:30 +04:00
// HACK this is used by the ahead-of-time compiler to overrule the bootstrap classloader
internal static void SetBootstrapClassLoader(ClassLoaderWrapper bootstrapClassLoader)
{
Debug.Assert(ClassLoaderWrapper.bootstrapClassLoader == null);
2002-12-18 19:00:25 +03:00
2005-06-01 13:49:30 +04:00
ClassLoaderWrapper.bootstrapClassLoader = bootstrapClassLoader;
}
2004-03-08 18:18:47 +03:00
2005-06-01 13:49:30 +04:00
static ClassLoaderWrapper()
{
typeToTypeWrapper[PrimitiveTypeWrapper.BOOLEAN.TypeAsTBD] = PrimitiveTypeWrapper.BOOLEAN;
typeToTypeWrapper[PrimitiveTypeWrapper.BYTE.TypeAsTBD] = PrimitiveTypeWrapper.BYTE;
typeToTypeWrapper[PrimitiveTypeWrapper.CHAR.TypeAsTBD] = PrimitiveTypeWrapper.CHAR;
typeToTypeWrapper[PrimitiveTypeWrapper.DOUBLE.TypeAsTBD] = PrimitiveTypeWrapper.DOUBLE;
typeToTypeWrapper[PrimitiveTypeWrapper.FLOAT.TypeAsTBD] = PrimitiveTypeWrapper.FLOAT;
typeToTypeWrapper[PrimitiveTypeWrapper.INT.TypeAsTBD] = PrimitiveTypeWrapper.INT;
typeToTypeWrapper[PrimitiveTypeWrapper.LONG.TypeAsTBD] = PrimitiveTypeWrapper.LONG;
typeToTypeWrapper[PrimitiveTypeWrapper.SHORT.TypeAsTBD] = PrimitiveTypeWrapper.SHORT;
typeToTypeWrapper[PrimitiveTypeWrapper.VOID.TypeAsTBD] = PrimitiveTypeWrapper.VOID;
LoadRemappedTypes();
}
2004-12-21 17:59:29 +03:00
2005-06-01 13:49:30 +04:00
internal static void LoadRemappedTypes()
2002-12-18 19:00:25 +03:00
{
2005-12-07 12:06:32 +03:00
// if we're compiling the core, coreAssembly will be null
Assembly coreAssembly = JVM.CoreAssembly;
if(coreAssembly != null)
2004-03-08 18:18:47 +03:00
{
2005-12-19 18:12:49 +03:00
Tracer.Info(Tracer.Runtime, "Core assembly: {0}", coreAssembly.Location);
2005-12-07 12:06:32 +03:00
RemappedClassAttribute[] remapped = AttributeHelper.GetRemappedClasses(coreAssembly);
2005-06-01 13:49:30 +04:00
if(remapped.Length > 0)
{
foreach(RemappedClassAttribute r in remapped)
{
Tracer.Info(Tracer.Runtime, "Remapping type {0} to {1}", r.RemappedType, r.Name);
remappedTypes.Add(r.RemappedType, r.Name);
}
}
else
2004-03-08 18:18:47 +03:00
{
2005-06-01 13:49:30 +04:00
JVM.CriticalFailure("Failed to find core classes in core library", null);
2004-03-08 18:18:47 +03:00
}
}
2002-12-18 19:00:25 +03:00
}
2004-03-08 18:18:47 +03:00
2005-06-01 13:49:30 +04:00
internal static bool IsCoreAssemblyType(Type type)
{
2005-12-07 12:06:32 +03:00
return type.Assembly == JVM.CoreAssembly;
2005-06-01 13:49:30 +04:00
}
2004-10-04 23:30:53 +04:00
2005-06-01 13:49:30 +04:00
internal ClassLoaderWrapper(object javaClassLoader)
2004-03-08 18:18:47 +03:00
{
2005-06-01 13:49:30 +04:00
SetJavaClassLoader(javaClassLoader);
2002-12-18 19:00:25 +03:00
}
2005-06-01 13:49:30 +04:00
internal void SetJavaClassLoader(object javaClassLoader)
2004-08-17 13:05:21 +04:00
{
2005-06-01 13:49:30 +04:00
this.javaClassLoader = javaClassLoader;
2004-08-17 13:05:21 +04:00
}
2005-06-01 13:49:30 +04:00
internal static bool IsRemappedType(Type type)
2004-08-17 13:05:21 +04:00
{
2005-06-01 13:49:30 +04:00
return remappedTypes.ContainsKey(type);
2004-08-17 13:05:21 +04:00
}
2002-12-18 19:00:25 +03:00
2005-06-01 13:49:30 +04:00
internal void SetRemappedType(Type type, TypeWrapper tw)
{
Debug.Assert(!types.ContainsKey(tw.Name));
types.Add(tw.Name, tw);
Debug.Assert(!typeToTypeWrapper.ContainsKey(type));
typeToTypeWrapper.Add(type, tw);
remappedTypes.Add(type, type);
}
2002-12-18 19:00:25 +03:00
2005-06-01 13:49:30 +04:00
// HACK return the TypeWrapper if it is already loaded
2005-07-25 18:34:22 +04:00
// (this exists solely for DynamicTypeWrapper.SetupGhosts and VMClassLoader.findLoadedClass)
2005-06-01 13:49:30 +04:00
internal TypeWrapper GetLoadedClass(string name)
2004-08-17 13:05:21 +04:00
{
2005-06-01 13:49:30 +04:00
lock(types.SyncRoot)
{
return (TypeWrapper)types[name];
}
2004-08-17 13:05:21 +04:00
}
2003-12-20 01:19:18 +03:00
2006-05-15 13:08:01 +04:00
private TypeWrapper RegisterInitiatingLoader(TypeWrapper tw)
2005-07-27 19:57:55 +04:00
{
2005-08-25 17:15:42 +04:00
if(tw == null || tw.IsUnloadable || tw.IsPrimitive)
2005-07-27 19:57:55 +04:00
return tw;
lock(types.SyncRoot)
{
object existing = types[tw.Name];
if(existing != tw)
{
if(existing != null)
{
throw new LinkageError("duplicate class definition: " + tw.Name);
}
if(tw.IsArray)
{
TypeWrapper elem = tw;
// TODO there should be a way to get the ultimate element type
// without creating all the intermediate types
while(elem.IsArray)
{
elem = elem.ElementTypeWrapper;
}
2006-05-15 13:08:01 +04:00
// HACK elem.ElementTypeWrapper as an evil side effect registers the initiating loader
// so if we're the same loader, don't do it again
if(!elem.IsPrimitive && !types.ContainsKey(elem.Name))
{
RegisterInitiatingLoader(elem);
}
2005-07-27 19:57:55 +04:00
}
// NOTE if types.ContainsKey(tw.Name) is true (i.e. the value is null),
// we currently have a DefineClass in progress on another thread and we've
// beaten that thread to the punch by loading the class from a parent class
// loader instead. This is ok as DefineClass will throw a LinkageError when
// it is done.
types[tw.Name] = tw;
}
}
return tw;
}
2005-12-07 12:06:32 +03:00
#if !COMPACT_FRAMEWORK
2006-03-11 21:35:29 +03:00
// HACK the proxyClassLoader is used to dynamically load classes in the boot class loader
// (e.g. when a Proxy is defined a boot class)
private static DynamicClassLoader proxyClassLoader;
2006-02-22 17:44:07 +03:00
internal virtual TypeWrapper DefineClass(ClassFile f, object protectionDomain)
2003-06-10 17:28:47 +04:00
{
2006-03-11 21:35:29 +03:00
lock(wrapperLock)
{
if(proxyClassLoader == null)
{
proxyClassLoader = new DynamicClassLoader(null);
}
}
return proxyClassLoader.DefineClass(f, protectionDomain);
2006-02-22 17:44:07 +03:00
}
internal TypeWrapper DefineNetExpType(string name, string assemblyName)
{
Debug.Assert(this == GetBootstrapClassLoader());
TypeWrapper type;
lock(types.SyncRoot)
2003-06-10 17:28:47 +04:00
{
2006-02-22 17:44:07 +03:00
// we need to check if we've already got it, because other classloaders than the bootstrap classloader may
// "define" NetExp types, there is a potential race condition if multiple classloaders try to define the
// same type simultaneously.
type = (TypeWrapper)types[name];
if(type != null)
2003-06-10 17:28:47 +04:00
{
2006-02-22 17:44:07 +03:00
return type;
}
}
// The sole purpose of the netexp class is to let us load the assembly that the class lives in,
// once we've done that, all types in it become visible.
Assembly asm;
try
{
asm = Assembly.Load(assemblyName);
}
catch(Exception x)
{
throw new NoClassDefFoundError(name + " (" + x.Message + ")");
}
// pre-compiled Java types can also live in a netexp referenced assembly,
// so we have to explicitly check for those
// (DotNetTypeWrapper.CreateDotNetTypeWrapper will refuse to return Java types).
Type t = GetJavaTypeFromAssembly(asm, name);
if(t != null)
{
return GetWrapperFromBootstrapType(t);
}
type = DotNetTypeWrapper.CreateDotNetTypeWrapper(name);
if(type == null)
{
throw new NoClassDefFoundError(name + " not found in " + assemblyName);
}
lock(types.SyncRoot)
{
TypeWrapper race = (TypeWrapper)types[name];
if(race == null)
{
types.Add(name, type);
2005-06-01 13:49:30 +04:00
}
else
{
2006-02-22 17:44:07 +03:00
type = race;
2003-06-10 17:28:47 +04:00
}
}
2006-02-22 17:44:07 +03:00
return type;
2003-06-10 17:28:47 +04:00
}
2005-12-07 12:06:32 +03:00
#endif
2003-06-10 17:28:47 +04:00
2005-06-01 13:49:30 +04:00
internal TypeWrapper LoadClassByDottedName(string name)
2003-03-17 17:02:46 +03:00
{
2005-08-25 11:46:57 +04:00
TypeWrapper type = LoadClassByDottedNameFast(name, true);
2005-06-01 13:49:30 +04:00
if(type != null)
{
return type;
}
throw new ClassNotFoundException(name);
2003-03-17 17:02:46 +03:00
}
2005-06-01 13:49:30 +04:00
internal TypeWrapper LoadClassByDottedNameFast(string name)
2005-08-25 11:46:57 +04:00
{
return LoadClassByDottedNameFast(name, false);
}
private TypeWrapper LoadClassByDottedNameFast(string name, bool throwClassNotFoundException)
2003-02-15 12:20:26 +03:00
{
2005-06-01 13:49:30 +04:00
// .NET 1.1 has a limit of 1024 characters for type names
2006-05-15 13:08:01 +04:00
if(name.Length >= 1024 || name.Length == 0)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
return null;
2002-12-18 19:00:25 +03:00
}
2005-06-01 13:49:30 +04:00
Profiler.Enter("LoadClassByDottedName");
try
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
TypeWrapper type;
lock(types.SyncRoot)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
type = (TypeWrapper)types[name];
if(type == null && types.ContainsKey(name))
2004-12-03 10:57:15 +03:00
{
2005-06-01 13:49:30 +04:00
// NOTE this can also happen if we (incorrectly) trigger a load of this class during
// the loading of the base class, so we print a trace message here.
Tracer.Error(Tracer.ClassLoading, "**** ClassCircularityError: {0} ****", name);
throw new ClassCircularityError(name);
2004-12-03 10:57:15 +03:00
}
}
2005-06-01 13:49:30 +04:00
if(type != null)
2004-12-03 10:57:15 +03:00
{
2005-06-01 13:49:30 +04:00
return type;
2004-12-03 10:57:15 +03:00
}
2005-06-01 13:49:30 +04:00
if(name.Length > 1 && name[0] == '[')
2004-12-03 10:57:15 +03:00
{
2005-06-01 13:49:30 +04:00
int dims = 1;
while(name[dims] == '[')
2003-08-26 15:24:17 +04:00
{
2005-06-01 13:49:30 +04:00
dims++;
2003-08-26 15:24:17 +04:00
}
2005-06-01 13:49:30 +04:00
if(name[dims] == 'L')
2002-12-29 19:27:00 +03:00
{
2005-06-01 13:49:30 +04:00
if(!name.EndsWith(";") || name.Length <= dims + 2 || name[dims + 1] == '[')
2003-08-21 14:06:34 +04:00
{
2005-06-01 13:49:30 +04:00
// malformed class name
return null;
2003-08-21 14:06:34 +04:00
}
2005-06-01 13:49:30 +04:00
string elemClass = name.Substring(dims + 1, name.Length - dims - 2);
type = LoadClassByDottedNameFast(elemClass);
if(type != null)
2003-08-21 14:06:34 +04:00
{
2005-12-07 12:06:32 +03:00
#if !COMPACT_FRAMEWORK
2005-11-01 17:01:42 +03:00
// HACK make sure we don't go through a user class loader when creating
// an array for a precompiled or .NET type
2006-03-23 14:57:41 +03:00
// (this is to compensate for the hack that returns the system class loader
// for precompiled classes or .NET types)
2005-11-01 17:01:42 +03:00
if (type is DynamicTypeWrapper)
{
type = type.GetClassLoader().CreateArrayType(name, type, dims);
}
else
2005-12-07 12:06:32 +03:00
#endif
2005-11-01 17:01:42 +03:00
{
type = GetBootstrapClassLoader().CreateArrayType(name, type, dims);
}
2003-08-21 14:06:34 +04:00
}
2005-07-27 19:57:55 +04:00
return RegisterInitiatingLoader(type);
2002-12-29 19:27:00 +03:00
}
2005-06-01 13:49:30 +04:00
if(name.Length != dims + 1)
{
// malformed class name
return null;
}
switch(name[dims])
2003-08-21 14:06:34 +04:00
{
2005-06-01 13:49:30 +04:00
case 'B':
2005-07-27 19:57:55 +04:00
return RegisterInitiatingLoader(GetBootstrapClassLoader().CreateArrayType(name, PrimitiveTypeWrapper.BYTE, dims));
2005-06-01 13:49:30 +04:00
case 'C':
2005-07-27 19:57:55 +04:00
return RegisterInitiatingLoader(GetBootstrapClassLoader().CreateArrayType(name, PrimitiveTypeWrapper.CHAR, dims));
2005-06-01 13:49:30 +04:00
case 'D':
2005-07-27 19:57:55 +04:00
return RegisterInitiatingLoader(GetBootstrapClassLoader().CreateArrayType(name, PrimitiveTypeWrapper.DOUBLE, dims));
2005-06-01 13:49:30 +04:00
case 'F':
2005-07-27 19:57:55 +04:00
return RegisterInitiatingLoader(GetBootstrapClassLoader().CreateArrayType(name, PrimitiveTypeWrapper.FLOAT, dims));
2005-06-01 13:49:30 +04:00
case 'I':
2005-07-27 19:57:55 +04:00
return RegisterInitiatingLoader(GetBootstrapClassLoader().CreateArrayType(name, PrimitiveTypeWrapper.INT, dims));
2005-06-01 13:49:30 +04:00
case 'J':
2005-07-27 19:57:55 +04:00
return RegisterInitiatingLoader(GetBootstrapClassLoader().CreateArrayType(name, PrimitiveTypeWrapper.LONG, dims));
2005-06-01 13:49:30 +04:00
case 'S':
2005-07-27 19:57:55 +04:00
return RegisterInitiatingLoader(GetBootstrapClassLoader().CreateArrayType(name, PrimitiveTypeWrapper.SHORT, dims));
2005-06-01 13:49:30 +04:00
case 'Z':
2005-07-27 19:57:55 +04:00
return RegisterInitiatingLoader(GetBootstrapClassLoader().CreateArrayType(name, PrimitiveTypeWrapper.BOOLEAN, dims));
2005-06-01 13:49:30 +04:00
default:
return null;
2003-08-21 14:06:34 +04:00
}
2005-06-01 13:49:30 +04:00
}
if(this == GetBootstrapClassLoader())
{
2006-05-15 13:08:01 +04:00
Type t = GetBootstrapTypeRaw(name);
if(t != null)
2003-08-21 14:06:34 +04:00
{
2006-05-15 13:08:01 +04:00
return RegisterInitiatingLoader(GetWrapperFromBootstrapType(t));
}
type = DotNetTypeWrapper.CreateDotNetTypeWrapper(name);
if(type != null)
{
Debug.Assert(type.Name == name, type.Name + " != " + name);
lock(types.SyncRoot)
2005-06-01 13:49:30 +04:00
{
2006-05-15 13:08:01 +04:00
// another thread may have beaten us to it and in that
// case we don't want to overwrite the previous one
TypeWrapper race = (TypeWrapper)types[name];
if(race == null)
2005-06-01 13:49:30 +04:00
{
2006-05-15 13:08:01 +04:00
types[name] = type;
2004-08-17 13:05:21 +04:00
}
else
{
2006-05-15 13:08:01 +04:00
type = race;
2004-08-17 13:05:21 +04:00
}
}
2006-05-15 13:08:01 +04:00
return type;
2003-08-21 14:06:34 +04:00
}
2006-05-15 13:08:01 +04:00
#if STATIC_COMPILER
// NOTE it is important that this is done last, because otherwise we will
// load the netexp generated fake types (e.g. delegate inner interface) instead
// of having DotNetTypeWrapper generating it.
type = GetTypeWrapperCompilerHook(name);
if(type != null)
2003-08-21 14:06:34 +04:00
{
2006-05-15 13:08:01 +04:00
return type;
2005-06-01 13:49:30 +04:00
}
2006-05-15 13:08:01 +04:00
#endif // STATIC_COMPILER
2005-06-01 13:49:30 +04:00
if(javaClassLoader == null)
{
return null;
2003-08-21 14:06:34 +04:00
}
2002-12-29 19:27:00 +03:00
}
2006-03-11 21:35:29 +03:00
#if !COMPACT_FRAMEWORK
2006-05-15 13:08:01 +04:00
// if we're here, we're not the bootstrap class loader and don't have a java class loader,
// that must mean that we're either the proxyClassLoader or a ReflectionOnly class loader
// and we should delegate to the bootstrap class loader
if(javaClassLoader == null)
2006-03-11 21:35:29 +03:00
{
return GetBootstrapClassLoader().LoadClassByDottedNameFast(name, throwClassNotFoundException);
}
#endif
2006-04-10 13:09:09 +04:00
#if !STATIC_COMPILER
2005-06-01 13:49:30 +04:00
// NOTE just like Java does (I think), we take the classloader lock before calling the loadClass method
lock(javaClassLoader)
2002-12-29 19:27:00 +03:00
{
2005-06-01 13:49:30 +04:00
Profiler.Enter("ClassLoader.loadClass");
try
{
type = (TypeWrapper)JVM.Library.loadClass(javaClassLoader, name);
}
2005-08-24 15:35:00 +04:00
catch(Exception x)
{
2005-08-25 11:46:57 +04:00
if(!throwClassNotFoundException
&& LoadClassCritical("java.lang.ClassNotFoundException").TypeAsBaseType.IsInstanceOfType(x))
{
return null;
}
2005-08-24 15:35:00 +04:00
throw new ClassLoadingException(IKVM.Runtime.Util.MapException(x));
}
2005-06-01 13:49:30 +04:00
finally
{
Profiler.Leave("ClassLoader.loadClass");
}
2005-08-01 14:27:06 +04:00
// NOTE to be safe, we register the initiating loader,
// while we're holding the lock on the class loader object
return RegisterInitiatingLoader(type);
2002-12-29 19:27:00 +03:00
}
2006-04-10 13:09:09 +04:00
#else
return null;
#endif
2005-06-01 13:49:30 +04:00
}
finally
{
Profiler.Leave("LoadClassByDottedName");
2002-12-18 19:00:25 +03:00
}
}
2005-06-01 13:49:30 +04:00
private TypeWrapper GetWrapperFromBootstrapType(Type type)
2005-01-03 11:26:21 +03:00
{
2005-06-01 13:49:30 +04:00
//Tracer.Info(Tracer.Runtime, "GetWrapperFromBootstrapType: {0}", type.FullName);
Debug.Assert(!type.IsArray, "!type.IsArray", type.FullName);
2005-12-07 12:06:32 +03:00
#if !COMPACT_FRAMEWORK
2005-06-01 13:49:30 +04:00
Debug.Assert(!(type.Assembly is AssemblyBuilder), "!(type.Assembly is AssemblyBuilder)", type.FullName);
2005-12-07 12:06:32 +03:00
#endif
2005-06-01 13:49:30 +04:00
// only the bootstrap classloader can own compiled types
Debug.Assert(this == GetBootstrapClassLoader(), "this == GetBootstrapClassLoader()", type.FullName);
2005-12-07 12:06:32 +03:00
bool javaType = AttributeHelper.IsJavaModule(type.Module);
2005-06-01 13:49:30 +04:00
string name;
2005-01-03 11:26:21 +03:00
if(javaType)
{
2005-06-01 13:49:30 +04:00
name = CompiledTypeWrapper.GetName(type);
2005-01-03 11:26:21 +03:00
}
else
{
2005-06-01 13:49:30 +04:00
name = DotNetTypeWrapper.GetName(type);
2005-01-03 11:26:21 +03:00
}
2005-06-01 13:49:30 +04:00
TypeWrapper wrapper;
2004-08-17 13:05:21 +04:00
lock(types.SyncRoot)
2003-08-13 19:00:41 +04:00
{
2005-06-01 13:49:30 +04:00
wrapper = (TypeWrapper)types[name];
}
2005-08-02 12:44:55 +04:00
if(wrapper != null)
{
if(wrapper.TypeAsTBD != type)
{
2006-05-15 13:08:01 +04:00
string msg = String.Format("\nTypename \"{0}\" is imported from multiple assemblies:\n{1}\n{2}\n", type.FullName, wrapper.TypeAsTBD.Assembly.FullName, type.Assembly.FullName);
2005-08-02 12:44:55 +04:00
JVM.CriticalFailure(msg, null);
}
}
else
2005-06-01 13:49:30 +04:00
{
if(javaType)
2004-08-17 13:05:21 +04:00
{
2005-06-01 13:49:30 +04:00
// since this type was compiled from Java source, we have to look for our
// attributes
wrapper = CompiledTypeWrapper.newInstance(name, type);
2004-08-17 13:05:21 +04:00
}
2005-01-03 11:26:21 +03:00
else
{
2006-05-15 13:08:01 +04:00
if(!DotNetTypeWrapper.IsAllowedOutside(type))
{
return null;
}
2005-06-01 13:49:30 +04:00
// since this type was not compiled from Java source, we don't need to
// look for our attributes, but we do need to filter unrepresentable
// stuff (and transform some other stuff)
wrapper = new DotNetTypeWrapper(type);
}
Debug.Assert(wrapper.Name == name, "wrapper.Name == name", type.FullName);
lock(types.SyncRoot)
{
// another thread may have beaten us to it and in that
// case we don't want to overwrite the previous one
TypeWrapper race = (TypeWrapper)types[name];
if(race == null)
{
types.Add(name, wrapper);
typeToTypeWrapper.Add(type, wrapper);
}
else
{
wrapper = race;
}
2005-01-03 11:26:21 +03:00
}
2003-08-13 19:00:41 +04:00
}
2005-06-01 13:49:30 +04:00
return wrapper;
2003-08-13 19:00:41 +04:00
}
2002-12-18 19:00:25 +03:00
2005-06-01 13:49:30 +04:00
// NOTE this method only sees pre-compiled Java classes
2006-05-15 13:08:01 +04:00
private Type GetBootstrapTypeRaw(string name)
2002-12-18 19:00:25 +03:00
{
2005-12-07 12:06:32 +03:00
#if COMPACT_FRAMEWORK
// TODO figure this out
return GetJavaTypeFromAssembly(JVM.CoreAssembly, name);
#else
Assembly[] assemblies;
#if WHIDBEY
2006-05-15 13:08:01 +04:00
if(JVM.IsStaticCompiler)
2005-12-07 12:06:32 +03:00
{
assemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
}
else
{
assemblies = AppDomain.CurrentDomain.GetAssemblies();
}
#else
assemblies = AppDomain.CurrentDomain.GetAssemblies();
#endif
foreach(Assembly a in assemblies)
2003-02-20 17:18:38 +03:00
{
2005-06-01 13:49:30 +04:00
if(!(a is AssemblyBuilder))
2004-01-11 16:14:42 +03:00
{
2005-06-01 13:49:30 +04:00
Type t = GetJavaTypeFromAssembly(a, name);
if(t != null)
{
return t;
}
2004-01-11 16:14:42 +03:00
}
2003-02-20 17:18:38 +03:00
}
2005-06-01 13:49:30 +04:00
return null;
2005-12-07 12:06:32 +03:00
#endif
2002-12-18 19:00:25 +03:00
}
2005-06-01 13:49:30 +04:00
private static Type GetJavaTypeFromAssembly(Assembly a, string name)
2004-11-16 14:11:53 +03:00
{
2005-06-27 13:06:57 +04:00
try
2005-06-01 13:49:30 +04:00
{
2005-06-27 13:06:57 +04:00
Type t = a.GetType(name);
2005-08-25 13:36:36 +04:00
if(t != null
2005-12-07 12:06:32 +03:00
&& AttributeHelper.IsJavaModule(t.Module)
2006-05-15 13:08:01 +04:00
&& !AttributeHelper.IsHideFromJava(t)
&& !t.IsArray
&& !t.IsPointer
&& !t.IsByRef)
2005-06-27 13:06:57 +04:00
{
return t;
}
// HACK we might be looking for an inner classes
t = a.GetType(name.Replace('$', '+'));
2005-08-25 13:36:36 +04:00
if(t != null
2005-12-07 12:06:32 +03:00
&& AttributeHelper.IsJavaModule(t.Module)
2006-05-15 13:08:01 +04:00
&& !AttributeHelper.IsHideFromJava(t)
&& !t.IsArray
&& !t.IsPointer
&& !t.IsByRef)
2005-06-27 13:06:57 +04:00
{
return t;
}
2005-06-01 13:49:30 +04:00
}
2005-06-27 13:06:57 +04:00
catch(ArgumentException x)
2005-06-01 13:49:30 +04:00
{
2005-06-27 13:06:57 +04:00
// we can end up here because we replace the $ with a plus sign
// (or client code did a Class.forName() on an invalid name)
Tracer.Info(Tracer.Runtime, x.Message);
2005-06-01 13:49:30 +04:00
}
2006-05-23 16:49:16 +04:00
catch(FileLoadException x)
{
// this can only happen if the assembly was loaded in the ReflectionOnly
// context and the requested type references a type in another assembly
// that cannot be found in the ReflectionOnly context
// TODO figure out what other exceptions Assembly.GetType() can throw
Tracer.Info(Tracer.Runtime, x.Message);
}
2005-06-01 13:49:30 +04:00
return null;
2004-11-16 14:11:53 +03:00
}
2002-12-18 19:00:25 +03:00
2006-05-15 13:08:01 +04:00
#if STATIC_COMPILER
2005-06-01 13:49:30 +04:00
internal virtual TypeWrapper GetTypeWrapperCompilerHook(string name)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
return null;
2005-01-03 11:26:21 +03:00
}
2006-05-15 13:08:01 +04:00
#endif // STATIC_COMPILER
2005-06-01 13:49:30 +04:00
// NOTE this method can actually return null if the resulting array type name would be too long
// for .NET to handle.
private TypeWrapper CreateArrayType(string name, TypeWrapper elementTypeWrapper, int dims)
2005-01-03 11:26:21 +03:00
{
2005-06-01 13:49:30 +04:00
Debug.Assert(new String('[', dims) + elementTypeWrapper.SigName == name);
Debug.Assert(!elementTypeWrapper.IsUnloadable && !elementTypeWrapper.IsVerifierType && !elementTypeWrapper.IsArray);
Debug.Assert(dims >= 1);
Type elementType = elementTypeWrapper.TypeAsArrayType;
TypeWrapper wrapper;
lock(types.SyncRoot)
2005-01-03 11:26:21 +03:00
{
2005-06-01 13:49:30 +04:00
wrapper = (TypeWrapper)types[name];
2005-01-03 11:26:21 +03:00
}
2005-06-01 13:49:30 +04:00
if(wrapper == null)
2005-01-03 11:26:21 +03:00
{
2005-06-01 13:49:30 +04:00
String netname = elementType.FullName + "[]";
for(int i = 1; i < dims; i++)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
netname += "[]";
}
// .NET 1.1 has a limit of 1024 characters for type names
if(netname.Length >= 1024)
{
return null;
}
Type array;
2005-12-07 12:06:32 +03:00
#if !COMPACT_FRAMEWORK
2005-06-01 13:49:30 +04:00
if(elementType.Module is ModuleBuilder)
{
// FXBUG ModuleBuilder.GetType() is broken (I think), it fires a TypeResolveEvent when
// you try to construct an array type from an unfinished type. I don't think it should
// do that. We have to work around that by setting a global flag (yuck) to prevent us
// from responding to the TypeResolveEvent.
lock(arrayConstructionLock)
{
arrayConstructionHack = true;
try
{
array = ((ModuleBuilder)elementType.Module).GetType(netname);
}
finally
{
arrayConstructionHack = false;
}
}
}
else
2005-12-07 12:06:32 +03:00
#endif
2005-06-01 13:49:30 +04:00
{
array = elementType.Assembly.GetType(netname, true);
}
Modifiers modifiers = Modifiers.Final | Modifiers.Abstract;
Modifiers reflectiveModifiers = modifiers;
modifiers |= elementTypeWrapper.Modifiers & Modifiers.Public;
reflectiveModifiers |= elementTypeWrapper.ReflectiveModifiers & Modifiers.AccessMask;
wrapper = new ArrayTypeWrapper(array, modifiers, reflectiveModifiers, name, this);
lock(types.SyncRoot)
{
// another thread may have beaten us to it and in that
// case we don't want to overwrite the previous one
TypeWrapper race = (TypeWrapper)types[name];
if(race == null)
2005-01-03 11:26:21 +03:00
{
2005-06-01 13:49:30 +04:00
types.Add(name, wrapper);
2005-12-07 12:06:32 +03:00
#if COMPACT_FRAMEWORK
if(!wrapper.IsGhostArray)
{
Debug.Assert(!typeToTypeWrapper.ContainsKey(array), name);
typeToTypeWrapper.Add(array, wrapper);
}
#else
2005-06-01 13:49:30 +04:00
if(!(elementType is TypeBuilder) && !wrapper.IsGhostArray)
{
Debug.Assert(!typeToTypeWrapper.ContainsKey(array), name);
typeToTypeWrapper.Add(array, wrapper);
}
2005-12-07 12:06:32 +03:00
#endif
2005-01-03 11:26:21 +03:00
}
2005-06-01 13:49:30 +04:00
else
2005-01-03 11:26:21 +03:00
{
2005-06-01 13:49:30 +04:00
wrapper = race;
2005-01-03 11:26:21 +03:00
}
2002-12-18 19:00:25 +03:00
}
2005-01-03 11:26:21 +03:00
}
2005-06-01 13:49:30 +04:00
return wrapper;
}
internal object GetJavaClassLoader()
2003-12-20 01:19:18 +03:00
{
2005-06-01 13:49:30 +04:00
return (this == GetBootstrapClassLoader()) ? null : javaClassLoader;
2005-01-03 11:26:21 +03:00
}
2005-06-01 13:49:30 +04:00
// When -Xbootclasspath is specified, we use a URLClassLoader as an
// additional bootstrap class loader (this is not visible to the Java code).
// We need to access this to be able to load resources.
internal static object GetJavaBootstrapClassLoader()
2005-01-03 11:26:21 +03:00
{
2005-06-01 13:49:30 +04:00
return GetBootstrapClassLoader().javaClassLoader;
2005-01-03 11:26:21 +03:00
}
2005-06-01 13:49:30 +04:00
internal TypeWrapper ExpressionTypeWrapper(string type)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
Debug.Assert(!type.StartsWith("Lret;"));
Debug.Assert(type != "Lnull");
int index = 0;
return SigDecoderWrapper(ref index, type);
2002-12-18 19:00:25 +03:00
}
2005-06-01 13:49:30 +04:00
// NOTE this exposes potentially unfinished types
internal Type[] ArgTypeListFromSig(string sig)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
if(sig[1] == ')')
{
2005-12-07 12:06:32 +03:00
#if COMPACT_FRAMEWORK
return new Type[0];
#else
2005-06-01 13:49:30 +04:00
return Type.EmptyTypes;
2005-12-07 12:06:32 +03:00
#endif
2005-06-01 13:49:30 +04:00
}
TypeWrapper[] wrappers = ArgTypeWrapperListFromSig(sig);
Type[] types = new Type[wrappers.Length];
for(int i = 0; i < wrappers.Length; i++)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
types[i] = wrappers[i].TypeAsSignatureType;
2002-12-18 19:00:25 +03:00
}
2005-06-01 13:49:30 +04:00
return types;
}
// NOTE: this will ignore anything following the sig marker (so that it can be used to decode method signatures)
private TypeWrapper SigDecoderWrapper(ref int index, string sig)
{
switch(sig[index++])
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
case 'B':
return PrimitiveTypeWrapper.BYTE;
case 'C':
return PrimitiveTypeWrapper.CHAR;
case 'D':
return PrimitiveTypeWrapper.DOUBLE;
case 'F':
return PrimitiveTypeWrapper.FLOAT;
case 'I':
return PrimitiveTypeWrapper.INT;
case 'J':
return PrimitiveTypeWrapper.LONG;
case 'L':
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
int pos = index;
index = sig.IndexOf(';', index) + 1;
return LoadClassByDottedName(sig.Substring(pos, index - pos - 1));
2002-12-18 19:00:25 +03:00
}
2005-06-01 13:49:30 +04:00
case 'S':
return PrimitiveTypeWrapper.SHORT;
case 'Z':
return PrimitiveTypeWrapper.BOOLEAN;
case 'V':
return PrimitiveTypeWrapper.VOID;
case '[':
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
// TODO this can be optimized
string array = "[";
while(sig[index] == '[')
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
index++;
array += "[";
}
switch(sig[index])
{
case 'L':
{
int pos = index;
index = sig.IndexOf(';', index) + 1;
return LoadClassByDottedName(array + sig.Substring(pos, index - pos));
}
case 'B':
case 'C':
case 'D':
case 'F':
case 'I':
case 'J':
case 'S':
case 'Z':
return LoadClassByDottedName(array + sig[index++]);
default:
throw new InvalidOperationException(sig.Substring(index));
2002-12-18 19:00:25 +03:00
}
}
2005-06-01 13:49:30 +04:00
default:
throw new InvalidOperationException(sig.Substring(index));
2002-12-18 19:00:25 +03:00
}
}
2004-03-16 20:10:09 +03:00
2005-06-01 13:49:30 +04:00
internal TypeWrapper FieldTypeWrapperFromSig(string sig)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
int index = 0;
return SigDecoderWrapper(ref index, sig);
2002-12-18 19:00:25 +03:00
}
2005-06-01 13:49:30 +04:00
internal TypeWrapper RetTypeWrapperFromSig(string sig)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
int index = sig.IndexOf(')') + 1;
return SigDecoderWrapper(ref index, sig);
2002-12-18 19:00:25 +03:00
}
2005-06-01 13:49:30 +04:00
internal TypeWrapper[] ArgTypeWrapperListFromSig(string sig)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
if(sig[1] == ')')
{
return TypeWrapper.EmptyArray;
}
ArrayList list = new ArrayList();
for(int i = 1; sig[i] != ')';)
2004-08-17 13:05:21 +04:00
{
2005-06-01 13:49:30 +04:00
list.Add(SigDecoderWrapper(ref i, sig));
2004-08-17 13:05:21 +04:00
}
2005-06-01 13:49:30 +04:00
TypeWrapper[] types = new TypeWrapper[list.Count];
list.CopyTo(types);
return types;
2002-12-18 19:00:25 +03:00
}
2004-09-09 15:17:55 +04:00
2005-06-01 13:49:30 +04:00
internal static ClassLoaderWrapper GetBootstrapClassLoader()
2004-10-04 23:30:53 +04:00
{
2006-03-23 14:57:41 +03:00
lock(wrapperLock)
2005-06-01 13:49:30 +04:00
{
if(bootstrapClassLoader == null)
{
2006-02-22 17:44:07 +03:00
bootstrapClassLoader = new BootstrapClassLoader();
2005-06-01 13:49:30 +04:00
}
return bootstrapClassLoader;
}
2004-10-04 23:30:53 +04:00
}
2005-06-01 13:49:30 +04:00
internal static ClassLoaderWrapper GetSystemClassLoader()
2004-09-09 15:17:55 +04:00
{
2006-04-10 13:09:09 +04:00
#if STATIC_COMPILER
2005-06-01 13:49:30 +04:00
// during static compilation, we don't have a system class loader
2006-04-10 13:09:09 +04:00
return GetBootstrapClassLoader();
#else
2005-06-01 13:49:30 +04:00
if(systemClassLoader == null)
{
systemClassLoader = GetClassLoaderWrapper(JVM.Library.getSystemClassLoader());
}
return systemClassLoader;
2006-04-10 13:09:09 +04:00
#endif
2004-09-09 15:17:55 +04:00
}
2006-04-10 13:09:09 +04:00
#if !STATIC_COMPILER
2005-06-01 13:49:30 +04:00
internal static ClassLoaderWrapper GetClassLoaderWrapper(object javaClassLoader)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
if(javaClassLoader == null || GetBootstrapClassLoader().javaClassLoader == javaClassLoader)
2004-08-17 13:05:21 +04:00
{
2005-06-01 13:49:30 +04:00
return GetBootstrapClassLoader();
}
2005-09-06 12:06:04 +04:00
lock(wrapperLock)
2005-06-01 13:49:30 +04:00
{
2005-09-06 12:06:04 +04:00
ClassLoaderWrapper wrapper = (ClassLoaderWrapper)JVM.Library.getWrapperFromClassLoader(javaClassLoader);
2005-06-01 13:49:30 +04:00
if(wrapper == null)
{
2006-02-22 17:44:07 +03:00
wrapper = new DynamicClassLoader(javaClassLoader);
2005-09-06 12:06:04 +04:00
JVM.Library.setWrapperForClassLoader(javaClassLoader, wrapper);
2005-06-01 13:49:30 +04:00
}
return wrapper;
2004-08-17 13:05:21 +04:00
}
2002-12-18 19:00:25 +03:00
}
2006-04-10 13:09:09 +04:00
#endif
2002-12-18 19:00:25 +03:00
2005-06-01 13:49:30 +04:00
// This only returns the wrapper for a Type if that wrapper has already been created, otherwise
// it returns null
// If the wrapper doesn't exist, that means that the type is either a .NET type or a pre-compiled Java class
2006-05-23 16:49:16 +04:00
private static TypeWrapper GetWrapperFromTypeFast(Type type)
2002-12-18 19:00:25 +03:00
{
2005-06-01 13:49:30 +04:00
TypeWrapper.AssertFinished(type);
2006-01-23 13:18:44 +03:00
Debug.Assert(!Whidbey.ContainsGenericParameters(type));
2005-06-01 13:49:30 +04:00
TypeWrapper wrapper = (TypeWrapper)typeToTypeWrapper[type];
if(wrapper == null)
2003-02-15 21:52:32 +03:00
{
2005-06-01 13:49:30 +04:00
string name = (string)remappedTypes[type];
if(name != null)
{
return LoadClassCritical(name);
}
2003-02-15 21:52:32 +03:00
}
2005-06-01 13:49:30 +04:00
return wrapper;
2003-02-15 21:52:32 +03:00
}
2005-06-01 13:49:30 +04:00
internal static TypeWrapper GetWrapperFromType(Type type)
2003-02-15 21:52:32 +03:00
{
2005-06-01 13:49:30 +04:00
//Tracer.Info(Tracer.Runtime, "GetWrapperFromType: {0}", type.AssemblyQualifiedName);
TypeWrapper.AssertFinished(type);
2006-01-23 13:18:44 +03:00
Debug.Assert(!Whidbey.ContainsGenericParameters(type));
2005-06-01 13:49:30 +04:00
TypeWrapper wrapper = GetWrapperFromTypeFast(type);
if(wrapper == null)
2003-02-17 13:13:16 +03:00
{
2005-06-01 13:49:30 +04:00
Debug.Assert(type != typeof(object) && type != typeof(string));
if(type.IsArray)
2003-02-17 13:13:16 +03:00
{
2005-06-01 13:49:30 +04:00
// it might be an array of a dynamically compiled Java type
int rank = 1;
Type elem = type.GetElementType();
while(elem.IsArray)
{
rank++;
elem = elem.GetElementType();
}
wrapper = GetWrapperFromType(elem);
return wrapper.MakeArrayType(rank);
2003-02-17 13:13:16 +03:00
}
2005-06-01 13:49:30 +04:00
// if the wrapper doesn't already exist, that must mean that the type
// is a .NET type (or a pre-compiled Java class), which means that it
2006-05-15 13:08:01 +04:00
// was "loaded" by an assembly classloader
return GetAssemblyClassLoader(type.Assembly).GetWrapperFromBootstrapType(type);
2003-02-17 13:13:16 +03:00
}
2005-06-01 13:49:30 +04:00
return wrapper;
2002-12-18 19:00:25 +03:00
}
2006-05-15 13:08:01 +04:00
// this method only supports .NET or pre-compiled Java assemblies
internal static ClassLoaderWrapper GetAssemblyClassLoader(Assembly assembly)
{
// TODO this assertion fires when compiling the core library (at least on Whidbey)
// I need to find out why...
Debug.Assert(!(assembly is AssemblyBuilder));
#if WHIDBEY && !STATIC_COMPILER
if(assembly.ReflectionOnly)
{
lock(reflectionOnlyClassLoaders)
{
ClassLoaderWrapper loader = (ClassLoaderWrapper)reflectionOnlyClassLoaders[assembly];
if(loader == null)
{
loader = new ReflectionOnlyClassLoader();
reflectionOnlyClassLoaders[assembly] = loader;
}
return loader;
}
}
#endif
return GetBootstrapClassLoader();
}
2005-06-01 13:49:30 +04:00
internal static void SetWrapperForType(Type type, TypeWrapper wrapper)
{
TypeWrapper.AssertFinished(type);
Debug.Assert(!typeToTypeWrapper.ContainsKey(type));
typeToTypeWrapper.Add(type, wrapper);
}
2004-11-23 20:46:39 +03:00
2006-04-10 13:09:09 +04:00
#if STATIC_COMPILER
2005-06-01 13:49:30 +04:00
internal static void PublishLibraryImplementationHelperType(Type type)
2003-12-24 14:51:41 +03:00
{
2005-06-01 13:49:30 +04:00
CompiledTypeWrapper typeWrapper = CompiledTypeWrapper.newInstance(type.FullName, type);
SetWrapperForType(type, typeWrapper);
GetBootstrapClassLoader().types[type.FullName] = typeWrapper;
2003-12-24 14:51:41 +03:00
}
2006-04-10 13:09:09 +04:00
#endif // STATIC_COMPILER
2005-06-01 13:49:30 +04:00
internal static TypeWrapper LoadClassCritical(string name)
2003-12-24 14:51:41 +03:00
{
2005-06-01 13:49:30 +04:00
try
{
return GetBootstrapClassLoader().LoadClassByDottedName(name);
}
catch(Exception x)
{
JVM.CriticalFailure("Loading of critical class failed", x);
return null;
}
2003-12-24 14:51:41 +03:00
}
2004-08-30 19:56:23 +04:00
2005-06-01 13:49:30 +04:00
internal void RegisterNativeLibrary(IntPtr p)
2004-08-30 19:56:23 +04:00
{
2005-06-01 13:49:30 +04:00
lock(this)
2004-08-30 19:56:23 +04:00
{
2005-06-01 13:49:30 +04:00
if(nativeLibraries == null)
{
nativeLibraries = new ArrayList();
}
nativeLibraries.Add(p);
2004-08-30 19:56:23 +04:00
}
}
2005-06-01 13:49:30 +04:00
internal IntPtr[] GetNativeLibraries()
2004-08-30 19:56:23 +04:00
{
2005-06-01 13:49:30 +04:00
lock(this)
2004-08-30 19:56:23 +04:00
{
2005-06-01 13:49:30 +04:00
if(nativeLibraries == null)
{
return new IntPtr[0];
}
return (IntPtr[])nativeLibraries.ToArray(typeof(IntPtr));
2004-08-30 19:56:23 +04:00
}
}
2005-02-23 15:56:15 +03:00
2005-06-01 13:49:30 +04:00
public override string ToString()
2005-02-23 15:56:15 +03:00
{
2005-06-01 13:49:30 +04:00
if(javaClassLoader == null)
{
return "null";
}
return String.Format("{0}@{1:X}", GetWrapperFromType(javaClassLoader.GetType()).Name, javaClassLoader.GetHashCode());
2005-02-23 15:56:15 +03:00
}
}
2006-02-22 17:44:07 +03:00
class BootstrapClassLoader : ClassLoaderWrapper
{
internal BootstrapClassLoader()
: base(null)
{
}
}
2006-05-15 13:08:01 +04:00
class ReflectionOnlyClassLoader : ClassLoaderWrapper
{
internal ReflectionOnlyClassLoader()
: base(null)
{
}
}
2002-12-18 19:00:25 +03:00
}