diff --git a/java/execution/impl/execution-impl.iml b/java/execution/impl/execution-impl.iml
index bf85ee694ec4..ed11ab8cf3e1 100644
--- a/java/execution/impl/execution-impl.iml
+++ b/java/execution/impl/execution-impl.iml
@@ -23,7 +23,9 @@
-
+
+
+
diff --git a/java/execution/impl/testDiscovery/build.xml b/java/execution/impl/testDiscovery/build.xml
new file mode 100644
index 000000000000..e86f1ceb6043
--- /dev/null
+++ b/java/execution/impl/testDiscovery/build.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/CoveragePremain.java b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/CoveragePremain.java
new file mode 100755
index 000000000000..3cce29a2038d
--- /dev/null
+++ b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/CoveragePremain.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.testme.instrumentation;
+
+
+import java.io.File;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class CoveragePremain {
+
+ public static void premain(String argsString, Instrumentation instrumentation) throws Exception {
+ final File lib = new File(getArchivePath()).getParentFile();
+ final URL[] urls = new URL[3];
+ urls[0] = fileToURL(new File(lib, "testDiscoveryInstrumenter.jar"));
+ urls[1] = fileToURL(new File(lib, "asm-all.jar"));
+ urls[2] = fileToURL(new File(lib, "trove4j.jar"));
+
+ final Class instrumentator = Class.forName("org.jetbrains.testme.instrumentation.TestDiscoveryInstrumentator", true, new URLClassLoader(urls) {
+ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ synchronized (this) {
+ Class result = findLoadedClass(name);
+ if (result == null) {
+ try {
+ result = findClass(name);
+ } catch (ClassNotFoundException e) {
+ //ignore, will try to find class in parent
+ }
+ }
+
+ if (result != null && resolve) {
+ resolveClass(result);
+ }
+
+ if (result != null) {
+ return result;
+ }
+ }
+
+ return getParent().loadClass(name);
+ }
+ });
+ final Method premainMethod = instrumentator.getDeclaredMethod("premain", new Class[]{String.class, Instrumentation.class});
+ premainMethod.invoke(null, new Object[] {argsString, instrumentation});
+ }
+
+ private static URL fileToURL(final File file) throws MalformedURLException {
+ return file.getAbsoluteFile().toURI().toURL();
+ }
+
+ private static String getArchivePath() {
+ final String className = CoveragePremain.class.getName().replace('.', '/') + ".class";
+ URL resourceURL = CoveragePremain.class.getResource("/" + className);
+ if (resourceURL == null) {
+ resourceURL = ClassLoader.getSystemResource(className);
+ }
+ return URLsUtil.extractRoot(resourceURL, "/" + className);
+ }
+}
\ No newline at end of file
diff --git a/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/InstrumentedMethodsFilter.java b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/InstrumentedMethodsFilter.java
new file mode 100644
index 000000000000..5786c953542a
--- /dev/null
+++ b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/InstrumentedMethodsFilter.java
@@ -0,0 +1,37 @@
+package org.jetbrains.testme.instrumentation;
+
+import org.jetbrains.org.objectweb.asm.Opcodes;
+
+public class InstrumentedMethodsFilter {
+ private final String myClassName;
+ private boolean myEnum;
+
+ public InstrumentedMethodsFilter(String className) {
+ myClassName = className;
+ }
+
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ myEnum = (access & Opcodes.ACC_ENUM) != 0;
+ }
+
+ public boolean shouldVisitMethod(final int access,
+ final String name,
+ final String desc,
+ final String signature,
+ final String[] exceptions) {
+ if ((access & Opcodes.ACC_BRIDGE) != 0) return false; //try to skip bridge methods
+ if ((access & Opcodes.ACC_ABSTRACT) != 0) return false; //skip abstracts; do not include interfaces without non-abstract methods in result
+ if ("".equals(name) || "".equals(name)) return false; //skip (static/instance) initializers
+
+ if (myEnum && isDefaultEnumMethod(name, desc, signature, myClassName)) {
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isDefaultEnumMethod(String name, String desc, String signature, String className) {
+ return name.equals("values") && desc.equals("()[L" + className + ";") ||
+ name.equals("valueOf") && desc.equals("(Ljava/lang/String;)L" + className + ";") ||
+ name.equals("") && signature != null && signature.equals("()V");
+ }
+}
diff --git a/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/Instrumenter.java b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/Instrumenter.java
new file mode 100755
index 000000000000..76f9c673c9e2
--- /dev/null
+++ b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/Instrumenter.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.testme.instrumentation;
+
+import org.jetbrains.org.objectweb.asm.ClassVisitor;
+import org.jetbrains.org.objectweb.asm.Label;
+import org.jetbrains.org.objectweb.asm.MethodVisitor;
+import org.jetbrains.org.objectweb.asm.Opcodes;
+
+public class Instrumenter extends ClassVisitor {
+ protected final ClassVisitor myClassVisitor;
+ private final String myClassName;
+ private final String myInternalClassName;
+ private final InstrumentedMethodsFilter myMethodFilter;
+ private final String[] myMethodNames;
+ private int myCurrentMethodCount;
+ private boolean myVisitedStaticBlock;
+
+ private static final String METHODS_VISITED = "__$methodsVisited$__";
+ private static final String METHODS_VISITED_CLASS = "[Z";
+
+ public Instrumenter(ClassVisitor classVisitor, String className, String[] methodNames) {
+ super(Opcodes.ASM5, classVisitor);
+ myClassVisitor = classVisitor;
+ myMethodFilter = new InstrumentedMethodsFilter(className);
+ myClassName = className.replace('$', '.'); // for inner classes
+ myInternalClassName = className.replace('.', '/');
+ myMethodNames = methodNames;
+ }
+
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ myMethodFilter.visit(version, access, name, signature, superName, interfaces);
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ public MethodVisitor visitMethod(final int access,
+ final String name,
+ final String desc,
+ final String signature,
+ final String[] exceptions) {
+ final MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
+ if (mv == null) return mv;
+ if ("".equals(name)) {
+ myVisitedStaticBlock = true;
+ return new StaticBlockMethodVisitor(mv);
+ }
+
+ if (!myMethodFilter.shouldVisitMethod(access, name, desc, signature, exceptions)) return mv;
+
+ assert myCurrentMethodCount < myMethodNames.length;
+
+ return new MethodVisitor(Opcodes.ASM5, mv) {
+ final int myMethodId = myCurrentMethodCount++;
+
+ public void visitCode() {
+ visitFieldInsn(Opcodes.GETSTATIC, myInternalClassName, METHODS_VISITED, METHODS_VISITED_CLASS);
+ pushInstruction(this, myMethodId);
+ visitInsn(Opcodes.ICONST_1);
+ visitInsn(Opcodes.BASTORE);
+
+ super.visitCode();
+ }
+ };
+ }
+
+ @Override
+ public void visitEnd() {
+ visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, METHODS_VISITED,
+ METHODS_VISITED_CLASS, null, null);
+
+ if (!myVisitedStaticBlock) {
+ MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null);
+ mv = new StaticBlockMethodVisitor(mv);
+ mv.visitCode();
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(myMethodNames.length + 2, 1);
+ mv.visitEnd();
+ }
+ super.visitEnd();
+ }
+
+ private class StaticBlockMethodVisitor extends MethodVisitor {
+ public StaticBlockMethodVisitor(MethodVisitor mv) {
+ super(Opcodes.ASM5, mv);
+ }
+
+ public void visitCode() {
+ super.visitCode();
+
+ pushInstruction(this, myMethodNames.length);
+ visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BOOLEAN);
+ visitFieldInsn(Opcodes.PUTSTATIC, myInternalClassName, METHODS_VISITED, METHODS_VISITED_CLASS);
+
+ pushInstruction(this, myMethodNames.length);
+
+ visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
+
+ for(int i = 0; i < myMethodNames.length; ++i) {
+ visitInsn(Opcodes.DUP);
+ pushInstruction(this, i);
+ visitLdcInsn(myMethodNames[i]);
+ visitInsn(Opcodes.AASTORE);
+ }
+
+ visitVarInsn(Opcodes.ASTORE, 0);
+
+ Label startLabel = new Label();
+ visitLabel(startLabel);
+
+ visitLdcInsn(myClassName);
+ visitFieldInsn(Opcodes.GETSTATIC, myInternalClassName, METHODS_VISITED, METHODS_VISITED_CLASS);
+ visitVarInsn(Opcodes.ALOAD, 0);
+ visitMethodInsn(Opcodes.INVOKESTATIC, ProjectData.PROJECT_DATA_OWNER, "trace", "(Ljava/lang/String;[Z[Ljava/lang/String;)V", false);
+
+ Label endLabel = new Label();
+ visitLabel(endLabel);
+
+ visitLocalVariable("methodNames", "[Ljava/lang/String;", null, startLabel, endLabel, 0);
+ // no return here
+ }
+
+ public void visitMaxs(int maxStack, int maxLocals) {
+ final int ourMaxStack = myMethodNames.length + 2;
+ final int ourMaxLocals = 1;
+
+ super.visitMaxs(Math.max(ourMaxStack, maxStack), Math.max(ourMaxLocals, maxLocals));
+ }
+ }
+
+ private static void pushInstruction(MethodVisitor mv, int operand) {
+ if (operand < Byte.MAX_VALUE) mv.visitIntInsn(Opcodes.BIPUSH, operand);
+ else mv.visitIntInsn(Opcodes.SIPUSH, operand);
+ }
+}
\ No newline at end of file
diff --git a/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/ProjectData.java b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/ProjectData.java
new file mode 100644
index 000000000000..31336bd6e1fc
--- /dev/null
+++ b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/ProjectData.java
@@ -0,0 +1,158 @@
+package org.jetbrains.testme.instrumentation;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+public class ProjectData {
+ public static final String PROJECT_DATA_OWNER = "org/jetbrains/testme/instrumentation/ProjectData";
+ public static final String TRACE_DIR = "org.jetbrains.testme.instrumentation.trace.dir";
+
+ protected static final ProjectData ourData = new ProjectData();
+
+ private String myTraceDir = System.getProperty(TRACE_DIR, "");
+
+ public void setTraceDir(String traceDir) {
+ myTraceDir = traceDir;
+ }
+
+ private ConcurrentMap> myTrace;
+ private final ConcurrentMap myTrace2 = new ConcurrentHashMap();
+ private final ConcurrentMap myTrace3 = new ConcurrentHashMap();
+
+ public static ProjectData getProjectData() {
+ return ourData;
+ }
+
+ public static void trace(String className, String methodSignature) {
+ ourData.traceLines(className, methodSignature);
+ }
+
+ // called from instrumented code during class's static init
+ public static void trace(String className, boolean[] methodFlags, String[] methodNames) {
+ ourData.traceLines(className, methodFlags, methodNames);
+ }
+
+ public void traceLines(String className, String methodSignature) {
+ if (myTrace != null) {
+ Set methods = myTrace.get(className);
+ if (methods == null) {
+ methods = new HashSet();
+ Set previousMethods = myTrace.putIfAbsent(className, methods);
+ if (previousMethods != null) methods = previousMethods;
+ }
+ synchronized (methods) {
+ methods.add(methodSignature);
+ }
+ }
+ }
+
+ public synchronized void traceLines(String className, boolean[] methodFlags, String[] methodNames) {
+ //System.out.println("Registering " + className);
+ assert methodFlags.length == methodNames.length;
+ myTrace2.put(className, methodFlags);
+ myTrace3.put(className, methodNames);
+ }
+
+ private static volatile boolean traceDirDumped;
+
+ public synchronized void testEnded(final String name) {
+ //if (myTrace == null) return;
+ if (!traceDirDumped) {
+ ClassLoader classLoader = TestDiscoveryInstrumentator.class.getClassLoader();
+ System.out.println(ourData + "; cl: " + classLoader+ "," + classLoader.getParent());
+ System.out.println("Trace dir:" + myTraceDir);
+ traceDirDumped = true;
+ }
+ new File(myTraceDir).mkdirs();
+ final File traceFile = new File(myTraceDir, name + ".tr");
+ try {
+ if (!traceFile.exists()) {
+ traceFile.createNewFile();
+ }
+ DataOutputStream os = null;
+ Deflater def = new Deflater(1);
+ try {
+ os = new DataOutputStream(new DeflaterOutputStream(new BufferedOutputStream(new FileOutputStream(traceFile)), def));
+
+ //saveOldTrace(os);
+
+ Map classToUsedMethods = new HashMap();
+ for(Map.Entry e: myTrace2.entrySet()) {
+ boolean[] used = e.getValue();
+ int usedMethodsCount = 0;
+
+ for (boolean anUsed : used) {
+ if (anUsed) ++usedMethodsCount;
+ }
+
+ if (usedMethodsCount > 0) {
+ classToUsedMethods.put(e.getKey(), usedMethodsCount);
+ }
+ }
+
+ os.writeInt(classToUsedMethods.size());
+ for(Map.Entry e: myTrace2.entrySet()) {
+ final boolean[] used = e.getValue();
+ final String className = e.getKey();
+
+ Integer integer = classToUsedMethods.get(className);
+ if (integer == null) continue;;
+
+ int usedMethodsCount = integer;
+
+ os.writeUTF(className);
+ os.writeInt(usedMethodsCount);
+
+ String[] methodNames = myTrace3.get(className);
+ for (int i = 0, len = used.length; i < len; ++i) {
+ // we check usedMethodCount here since used was observed to change // ?
+ if (used[i] && usedMethodsCount-- > 0) os.writeUTF(methodNames[i]);
+ }
+ }
+ }
+ finally {
+ if (os != null) {
+ os.close();
+ }
+ def.end();
+ }
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ finally {
+ myTrace = null;
+ }
+ }
+
+ private void saveOldTrace(DataOutputStream os) throws IOException {
+ os.writeInt(myTrace.size());
+ for (Iterator it = myTrace.keySet().iterator(); it.hasNext();) {
+ final String classData = it.next();
+ os.writeUTF(classData);
+ final Set methods = myTrace.get(classData);
+ os.writeInt(methods.size());
+ for (Iterator iterator = methods.iterator(); iterator.hasNext(); ) {
+ os.writeUTF(iterator.next());
+ }
+ }
+ }
+
+ public synchronized void testStarted(final String name) {
+ //clearOldTrace();
+ for(Map.Entry e: myTrace2.entrySet()) {
+ boolean[] used = e.getValue();
+ for(int i = 0, len = used.length; i < len; ++i) {
+ if(used[i]) used[i] = false;
+ }
+ }
+ }
+
+ private void clearOldTrace() {
+ myTrace = new ConcurrentHashMap>();
+ }
+}
diff --git a/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/TestDiscoveryInstrumentator.java b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/TestDiscoveryInstrumentator.java
new file mode 100755
index 000000000000..71d91feb7743
--- /dev/null
+++ b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/TestDiscoveryInstrumentator.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.testme.instrumentation;
+
+import org.jetbrains.org.objectweb.asm.*;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.instrument.Instrumentation;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+
+public class TestDiscoveryInstrumentator {
+
+ public static void premain(String argsString, Instrumentation instrumentation) throws Exception {
+ instrumentation.addTransformer(new ClassFileTransformer() {
+ private boolean computeFrames = computeFrames();
+
+ public byte[] transform(ClassLoader loader,
+ String className,
+ Class classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer) throws IllegalClassFormatException {
+ try {
+ if (className == null) {
+ return null;
+ }
+ if (loader == null) {
+ // skip classes loaded by system classloader
+ //System.out.println("Skipping " + className);
+ return null;
+ }
+ if (className.endsWith(".class")) {
+ className = className.substring(0, className.length() - 6);
+ }
+ className = className.replace('\\', '.').replace('/', '.');
+
+ if (className.startsWith("com.intellij.rt.")
+ || className.startsWith("com.intellij.util.lang.")
+ || className.startsWith("com.intellij.util.containers.")
+ || className.startsWith("com.intellij.openapi.util.text.")
+ || className.startsWith("com.intellij.openapi.util.io.")
+ || className.startsWith("java.")
+ || className.startsWith("sun.")
+ || className.startsWith("gnu.trove.")
+ || className.startsWith("org.jetbrains.org.objectweb.asm.")
+ || className.startsWith("org.apache.oro.text.regex.")
+ || className.startsWith("org.jetbrains.testme.")
+ || className.startsWith("org.junit.")
+ || className.startsWith("com.sun.")
+ || className.startsWith("junit.")
+ || className.startsWith("jdk.internal.")
+ || className.startsWith("com.intellij.junit3.")
+ || className.startsWith("com.intellij.junit4.")) {
+ return null;
+ }
+ //System.out.println(className);
+ return instrument(classfileBuffer, className, loader, computeFrames);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private boolean computeFrames() {
+ return System.getProperty("idea.coverage.no.frames") == null;
+ }
+ });
+ }
+
+ private final static AtomicInteger myInstrumentedClasses = new AtomicInteger();
+ private final static AtomicInteger myInstrumentedMethods = new AtomicInteger();
+ private final static AtomicLong myInstrumentedClassesTime = new AtomicLong();
+
+ private static byte[] instrument(final byte[] classfileBuffer, final String className, ClassLoader loader, boolean computeFrames) {
+ long started = System.nanoTime();
+ final ClassReader cr = new ClassReader(classfileBuffer);
+ final ClassWriter cw;
+ if (computeFrames && false) {
+ final int version = getClassFileVersion(cr);
+ cw = getClassWriter(version >= Opcodes.V1_6 && version != Opcodes.V1_1 ? ClassWriter.COMPUTE_FRAMES : ClassWriter.COMPUTE_MAXS, loader);
+ } else {
+ cw = getClassWriter(ClassWriter.COMPUTE_MAXS, loader);
+ }
+
+ final List instrumentedMethods = new ArrayList();
+
+ final ClassVisitor instrumentedMethodCounter = new ClassVisitor(Opcodes.ASM5) {
+ final InstrumentedMethodsFilter methodsFilter = new InstrumentedMethodsFilter(className);
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ methodsFilter.visit(version, access, name, signature, superName, interfaces);
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ if (methodsFilter.shouldVisitMethod(access, name, desc, signature, exceptions)) {
+ instrumentedMethods.add(name);
+ }
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+ };
+
+ cr.accept(instrumentedMethodCounter, 0);
+
+ // todo there are duplicates in array of instrumented methods
+ final ClassVisitor cv = new Instrumenter(cw, className, instrumentedMethods.toArray(new String[instrumentedMethods.size()]));
+ cr.accept(cv, 0);
+ byte[] bytes = cw.toByteArray();
+
+ long time = myInstrumentedClassesTime.addAndGet(System.nanoTime() - started);
+ int classes = myInstrumentedClasses.incrementAndGet();
+ int methods = myInstrumentedMethods.addAndGet(instrumentedMethods.size());
+ if (classes % 1000 == 0) {
+ System.out.println("Done instrumenting " + classes + ", methods:" + methods + " for " + (time / 1000000));
+ }
+
+ if (false) {
+ try {
+ FileOutputStream fileOutputStream = new FileOutputStream("transformed-" + className);
+ try {
+ fileOutputStream.write(bytes);
+ fileOutputStream.close();
+ } finally {
+ fileOutputStream.close();
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ return bytes;
+ }
+
+ private static ClassWriter getClassWriter(int flags, final ClassLoader classLoader) {
+ return new MyClassWriter(flags, classLoader);
+ }
+
+ public static int getClassFileVersion(ClassReader reader) {
+ final int[] classFileVersion = new int[1];
+ reader.accept(new ClassVisitor(Opcodes.ASM5) {
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ classFileVersion[0] = version;
+ }
+ }, 0);
+ return classFileVersion[0];
+ }
+
+ private static class MyClassWriter extends ClassWriter {
+ public static final String JAVA_LANG_OBJECT = "java/lang/Object";
+ private final ClassLoader classLoader;
+
+ public MyClassWriter(int flags, ClassLoader classLoader) {
+ super(flags);
+ this.classLoader = classLoader;
+ }
+
+ protected String getCommonSuperClass(String type1, String type2) {
+ try {
+ ClassReader info1 = typeInfo(type1);
+ ClassReader info2 = typeInfo(type2);
+ String
+ superType = checkImplementInterface(type1, type2, info1, info2);
+ if (superType != null) return superType;
+ superType = checkImplementInterface(type2, type1, info2, info1);
+ if (superType != null) return superType;
+
+ StringBuilder b1 = typeAncestors(type1, info1);
+ StringBuilder b2 = typeAncestors(type2, info2);
+ String result = JAVA_LANG_OBJECT;
+ int end1 = b1.length();
+ int end2 = b2.length();
+ while (true) {
+ int start1 = b1.lastIndexOf(";", end1 - 1);
+ int start2 = b2.lastIndexOf(";", end2 - 1);
+ if (start1 != -1 && start2 != -1 && end1 - start1 == end2 - start2) {
+ String p1 = b1.substring(start1 + 1, end1);
+ String p2 = b2.substring(start2 + 1, end2);
+ if (p1.equals(p2)) {
+ result = p1;
+ end1 = start1;
+ end2 = start2;
+ } else {
+ return result;
+ }
+ } else {
+ return result;
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ private String checkImplementInterface(String type1, String type2, ClassReader info1, ClassReader info2) throws IOException {
+ if ((info1.getAccess() & Opcodes.ACC_INTERFACE) != 0) {
+ if (typeImplements(type2, info2, type1)) {
+ return type1;
+ }
+ return JAVA_LANG_OBJECT;
+ }
+ return null;
+ }
+
+ private StringBuilder typeAncestors(String type, ClassReader info) throws IOException {
+ StringBuilder b = new StringBuilder();
+ while (!JAVA_LANG_OBJECT.equals(type)) {
+ b.append(';').append(type);
+ type = info.getSuperName();
+ info = typeInfo(type);
+ }
+ return b;
+ }
+
+ private boolean typeImplements(String type, ClassReader classReader, String interfaceName) throws IOException {
+ while (!JAVA_LANG_OBJECT.equals(type)) {
+ String[] itfs = classReader.getInterfaces();
+ for (int i = 0; i < itfs.length; ++i) {
+ if (itfs[i].equals(interfaceName)) {
+ return true;
+ }
+ }
+ for (int i = 0; i < itfs.length; ++i) {
+ if (typeImplements(itfs[i], typeInfo(itfs[i]), interfaceName)) {
+ return true;
+ }
+ }
+ type = classReader.getSuperName();
+ classReader = typeInfo(type);
+ }
+ return false;
+ }
+
+ private ClassReader typeInfo(final String type) throws IOException {
+ InputStream is = classLoader.getResourceAsStream(type + ".class");
+ if (is == null) System.out.println(classLoader + "," + type + ".class");
+ try {
+ return new ClassReader(is);
+ } finally {
+ is.close();
+ }
+ }
+ }
+}
diff --git a/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/URLsUtil.java b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/URLsUtil.java
new file mode 100644
index 000000000000..05510fe75f45
--- /dev/null
+++ b/java/execution/impl/testDiscovery/org/jetbrains/testme/instrumentation/URLsUtil.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.testme.instrumentation;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+public class URLsUtil {
+ public static final String FILE = "file";
+ public static final String PROTOCOL_DELIMITER = ":";
+ public static final String JAR_DELIMITER = "!";
+
+ public static boolean startsWithChar(CharSequence s, char prefix) {
+return s != null && s.length() != 0 && s.charAt(0) == prefix;
+}
+
+ public static String extractRoot(URL resourceURL, String resourcePath) {
+ if (!(startsWithChar(resourcePath, '/') || startsWithChar(resourcePath, '\\'))) {
+ //noinspection HardCodedStringLiteral
+ System.err.println("precondition failed: "+resourcePath);
+ return null;
+ }
+ String protocol = resourceURL.getProtocol();
+ String resultPath = null;
+
+ if (FILE.equals(protocol)) {
+ String path = resourceURL.getFile();
+ final String testPath = path.replace('\\', '/');
+ final String testResourcePath = resourcePath.replace('\\', '/');
+ if (endsWithIgnoreCase(testPath, testResourcePath)) {
+ resultPath = path.substring(0, path.length() - resourcePath.length());
+ }
+ }
+ else if ("jar".equals(protocol)) {
+ String fullPath = resourceURL.getFile();
+ int delimiter = fullPath.indexOf(JAR_DELIMITER);
+ if (delimiter >= 0) {
+ String archivePath = fullPath.substring(0, delimiter);
+ if (startsWithConcatenationOf(archivePath, FILE, PROTOCOL_DELIMITER)) {
+ resultPath = archivePath.substring(FILE.length() + PROTOCOL_DELIMITER.length());
+ }
+ }
+ }
+ if (resultPath == null) {
+ //noinspection HardCodedStringLiteral
+ System.err.println("cannot extract: "+resultPath + " from "+resourceURL);
+ return null;
+ }
+
+ if (resourcePath.endsWith(File.separator)) {
+ resultPath = resultPath.substring(0, resultPath.lastIndexOf(File.separator));
+ }
+ resultPath = unescapePercentSequences(resultPath);
+ return resultPath;
+ }
+
+ public static boolean startsWithConcatenationOf(String testee, String firstPrefix, String secondPrefix) {
+ int l1 = firstPrefix.length();
+ int l2 = secondPrefix.length();
+ if (testee.length() < l1 + l2) return false;
+ return testee.startsWith(firstPrefix) && testee.regionMatches(l1, secondPrefix, 0, l2);
+ }
+
+ public static boolean endsWithIgnoreCase(String str, String suffix) {
+ final int stringLength = str.length();
+ final int suffixLength = suffix.length();
+ return stringLength >= suffixLength && str.regionMatches(true, stringLength - suffixLength, suffix, 0, suffixLength);
+ }
+
+ public static String unescapePercentSequences(String s) {
+ if (s.indexOf('%') == -1) {
+ return s;
+ }
+
+ StringBuilder decoded = new StringBuilder();
+ final int len = s.length();
+ int i = 0;
+ while (i < len) {
+ char c = s.charAt(i);
+ if (c == '%') {
+ List bytes = new ArrayList();
+ while (i + 2 < len && s.charAt(i) == '%') {
+ final int d1 = decode(s.charAt(i + 1));
+ final int d2 = decode(s.charAt(i + 2));
+ if (d1 != -1 && d2 != -1) {
+ bytes.add(new Integer(((d1 & 0xf) << 4 | d2 & 0xf)));
+ i += 3;
+ } else {
+ break;
+ }
+ }
+ if (!bytes.isEmpty()) {
+ final byte[] bytesArray = new byte[bytes.size()];
+ for (int j = 0; j < bytes.size(); j++) {
+ bytesArray[j] = (byte) ((Integer) bytes.get(j)).intValue();
+ }
+ try {
+ decoded.append(new String(bytesArray, "UTF-8"));
+ continue;
+ }
+ catch (UnsupportedEncodingException ignored) {
+ }
+ }
+ }
+
+ decoded.append(c);
+ i++;
+ }
+ return decoded.toString();
+ }
+
+ private static int decode(char c) {
+ if ((c >= '0') && (c <= '9'))
+ return c - '0';
+ if ((c >= 'a') && (c <= 'f'))
+ return c - 'a' + 10;
+ if ((c >= 'A') && (c <= 'F'))
+ return c - 'A' + 10;
+ return -1;
+ }
+}
\ No newline at end of file