1e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov/*******************************************************************************
2398ee59bebad6835dab57b60157eff16d511709eMarc R. Hoffmann * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors
3e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * All rights reserved. This program and the accompanying materials
4e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * are made available under the terms of the Eclipse Public License v1.0
5e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * which accompanies this distribution, and is available at
6e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * http://www.eclipse.org/legal/epl-v10.html
7e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov *
8e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * Contributors:
9e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov *    Marc R. Hoffmann - initial API and implementation
10e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov *
11e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov *******************************************************************************/
12e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovpackage org.jacoco.core.runtime;
13e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
14e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport java.util.logging.Handler;
15e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport java.util.logging.Level;
16e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport java.util.logging.LogRecord;
17e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport java.util.logging.Logger;
18e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
19e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport org.jacoco.core.internal.instr.InstrSupport;
20e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport org.objectweb.asm.MethodVisitor;
21e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport org.objectweb.asm.Opcodes;
22e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
23e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov/**
24e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * This {@link IRuntime} implementation uses the Java logging API to report
25e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * coverage data.
26e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * <p>
27e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov *
28e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * The implementation uses a dedicated log channel. Instrumented classes call
29e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * {@link Logger#log(Level, String, Object[])} with the class identifier in the
30e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * first slot of the parameter array. The runtime implements a {@link Handler}
31e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * for this channel that puts the probe data structure into the first slot of
32e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * the parameter array.
33e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov */
34e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovpublic class LoggerRuntime extends AbstractRuntime {
35e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
36e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	private static final String CHANNEL = "jacoco-runtime";
37e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
38e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	private final String key;
39e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
40e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	private final Logger logger;
41e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
42e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	private final Handler handler;
43e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
44e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	/**
45e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	 * Creates a new runtime.
46e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	 */
47e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	public LoggerRuntime() {
48e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		super();
49e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		this.key = Integer.toHexString(hashCode());
50e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		this.logger = configureLogger();
51e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		this.handler = new RuntimeHandler();
52e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	}
53e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
54e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	private Logger configureLogger() {
55e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		final Logger l = Logger.getLogger(CHANNEL);
56e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		l.setUseParentHandlers(false);
57e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		l.setLevel(Level.ALL);
58e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		return l;
59e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	}
60e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
61e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	public int generateDataAccessor(final long classid, final String classname,
62e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			final int probecount, final MethodVisitor mv) {
63e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
64e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// The data accessor performs the following steps:
65e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		//
66e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// final Object[] args = new Object[3];
67e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// args[0] = Long.valueOf(classid);
68e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// args[1] = classname;
69e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// args[2] = Integer.valueOf(probecount);
70e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Logger.getLogger(CHANNEL).log(Level.INFO, key, args);
71e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// final byte[] probedata = (byte[]) args[0];
72e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		//
73e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Note that local variable 'args' is used at two places. As were not
74e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// allowed to allocate local variables we have to keep this value with
75e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// DUP and SWAP operations on the operand stack.
76e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
77e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// 1. Create parameter array:
78e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
7927e32e433ba0ac1fdec9290d3454d5b369c969ccMarc R. Hoffmann		RuntimeData.generateArgumentArray(classid, classname, probecount, mv);
80e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
81e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[0]: [Ljava/lang/Object;
82e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
83e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitInsn(Opcodes.DUP);
84e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
85e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[1]: [Ljava/lang/Object;
86e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[0]: [Ljava/lang/Object;
87e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
88e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// 2. Call Logger:
89e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
90e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitLdcInsn(CHANNEL);
91e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
92351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann				"getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;",
93351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann				false);
94e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
95e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[2]: Ljava/util/logging/Logger;
96e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[1]: [Ljava/lang/Object;
97e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[0]: [Ljava/lang/Object;
98e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
99e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitInsn(Opcodes.SWAP);
100e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
101e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[2]: [Ljava/lang/Object;
102e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[1]: Ljava/util/logging/Logger;
103e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[0]: [Ljava/lang/Object;
104e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
105e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO",
106e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov				"Ljava/util/logging/Level;");
107e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
108e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[3]: Ljava/util/logging/Level;
109e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[2]: [Ljava/lang/Object;
110e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[1]: Ljava/util/logging/Logger;
111e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[0]: [Ljava/lang/Object;
112e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
113e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitInsn(Opcodes.SWAP);
114e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
115e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[3]: [Ljava/lang/Object;
116e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[2]: Ljava/util/logging/Level;
117e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[1]: Ljava/util/logging/Logger;
118e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[0]: [Ljava/lang/Object;
119e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
120e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitLdcInsn(key);
121e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
122e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[4]: Ljava/lang/String;
123e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[3]: [Ljava/lang/Object;
124e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[2]: Ljava/util/logging/Level;
125e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[1]: Ljava/util/logging/Logger;
126e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[0]: [Ljava/lang/Object;
127e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
128e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitInsn(Opcodes.SWAP);
129e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
130e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[4]: [Ljava/lang/Object;
131e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[3]: Ljava/lang/String;
132e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[2]: Ljava/util/logging/Level;
133e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[1]: Ljava/util/logging/Logger;
134e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[0]: [Ljava/lang/Object;
135e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
136351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann		mv.visitMethodInsn(
137351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann				Opcodes.INVOKEVIRTUAL,
138351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann				"java/util/logging/Logger",
139e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov				"log",
140351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann				"(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V",
141351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann				false);
142e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
143e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[0]: [Ljava/lang/Object;
144e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
145e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// 3. Load data structure from parameter array:
146e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
147e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitInsn(Opcodes.ICONST_0);
148e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitInsn(Opcodes.AALOAD);
149e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		mv.visitTypeInsn(Opcodes.CHECKCAST, InstrSupport.DATAFIELD_DESC);
150e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
151e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		// Stack[0]: [Z
152e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
153e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		return 5; // Maximum local stack size is 5
154e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	}
155e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
15627e32e433ba0ac1fdec9290d3454d5b369c969ccMarc R. Hoffmann	@Override
15727e32e433ba0ac1fdec9290d3454d5b369c969ccMarc R. Hoffmann	public void startup(final RuntimeData data) throws Exception {
15827e32e433ba0ac1fdec9290d3454d5b369c969ccMarc R. Hoffmann		super.startup(data);
159e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		this.logger.addHandler(handler);
160e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	}
161e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
162e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	public void shutdown() {
163e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		this.logger.removeHandler(handler);
164e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	}
165e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
166e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	private class RuntimeHandler extends Handler {
167e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
168e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		@Override
169e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		public void publish(final LogRecord record) {
170e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			if (key.equals(record.getMessage())) {
17127e32e433ba0ac1fdec9290d3454d5b369c969ccMarc R. Hoffmann				data.getProbes(record.getParameters());
172e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			}
173e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		}
174e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
175e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		@Override
176e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		public void flush() {
177e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			// nothing to do
178e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		}
179e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
180e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		@Override
181e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		public void close() throws SecurityException {
182e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			// The Java logging framework removes and closes all handlers on JVM
183e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			// shutdown. As soon as our handler has been removed, all classes
184e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			// that might get instrumented during shutdown (e.g. loaded by other
185e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			// shutdown hooks) will fail to initialize. Therefore we add ourself
186e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			// again here. This is a nasty hack that might fail in some Java
187e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			// implementations.
188e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov			logger.addHandler(handler);
189e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov		}
190e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov	}
191e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov
192e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov}
193