diff --git a/.idea/modules.xml b/.idea/modules.xml
index 94ca5a1252c3..6443efd723a0 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -63,6 +63,7 @@
+
diff --git a/build/scripts/layouts.gant b/build/scripts/layouts.gant
index c97c321eeeb1..32e1658a0b3a 100644
--- a/build/scripts/layouts.gant
+++ b/build/scripts/layouts.gant
@@ -192,6 +192,9 @@ def layoutFull(String home, String targetDirectory, String patchedDescriptorDir
module("IntelliLang-xml")
module("IntelliLang-javaee")
}
+ jar("intellilang-jps-plugin.jar") {
+ module("intellilang-jps-plugin")
+ }
}
}
}
diff --git a/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java b/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java
index 78898be2bb23..a5d56e4e632a 100644
--- a/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java
+++ b/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java
@@ -100,7 +100,25 @@ public class InstrumentationClassFinder {
if (aClass != null) {
return aClass;
}
-
+
+ final InputStream is = getClassBytesAsStream(internalName);
+
+ if (is == null) {
+ throw new ClassNotFoundException("Class not found: " + internalName);
+ }
+
+ try {
+ final PseudoClass result = loadPseudoClass(is);
+ myLoaded.put(internalName, result);
+ return result;
+ }
+ finally {
+ is.close();
+ }
+ }
+
+ public InputStream getClassBytesAsStream(String className) throws IOException {
+ final String internalName = className.replace('.', '/'); // normalize
InputStream is = null;
// first look into platformCp
final String resourceName = internalName + CLASS_RESOURCE_EXTENSION;
@@ -123,19 +141,7 @@ public class InstrumentationClassFinder {
if (is == null) {
is = lookupClassAfterClasspath(internalName);
}
-
- if (is == null) {
- throw new ClassNotFoundException("Class not found: " + internalName);
- }
-
- try {
- final PseudoClass result = loadPseudoClass(is);
- myLoaded.put(internalName, result);
- return result;
- }
- finally {
- is.close();
- }
+ return is;
}
public InputStream getResourceAsStream(String resourceName) throws IOException {
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/intellilang-jps-plugin.iml b/plugins/IntelliLang/intellilang-jps-plugin/intellilang-jps-plugin.iml
new file mode 100644
index 000000000000..58257c150340
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/intellilang-jps-plugin.iml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService b/plugins/IntelliLang/intellilang-jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService
new file mode 100644
index 000000000000..41b8d9f78385
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService
@@ -0,0 +1 @@
+org.jetbrains.jps.intellilang.instrumentation.PatternValidatorBuilderService
\ No newline at end of file
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/META-INF/services/org.jetbrains.jps.intellilang.model.JpsIntelliLangExtensionService b/plugins/IntelliLang/intellilang-jps-plugin/src/META-INF/services/org.jetbrains.jps.intellilang.model.JpsIntelliLangExtensionService
new file mode 100644
index 000000000000..c22fada5abbd
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/META-INF/services/org.jetbrains.jps.intellilang.model.JpsIntelliLangExtensionService
@@ -0,0 +1 @@
+org.jetbrains.jps.intellilang.model.impl.JpsIntelliLangExtensionServiceImpl
\ No newline at end of file
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension b/plugins/IntelliLang/intellilang-jps-plugin/src/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension
new file mode 100644
index 000000000000..7dc93666d56a
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension
@@ -0,0 +1 @@
+org.jetbrains.jps.intellilang.model.impl.JpsIntelliLangModelSerializerExtension
\ No newline at end of file
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/InstrumentationAdapter.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/InstrumentationAdapter.java
new file mode 100644
index 000000000000..738b091798d9
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/InstrumentationAdapter.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2000-2012 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.jps.intellilang.instrumentation;
+
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.asm4.*;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+class InstrumentationAdapter extends MethodVisitor implements Opcodes {
+ @NonNls
+ private static final String RETURN_VALUE_NAME = "$returnvalue$";
+
+ private final Type[] myArgTypes;
+ private final Type myReturnType;
+ private final int myAccess;
+ private final String myMethodName;
+
+ private final PatternInstrumenter myInstrumenter;
+
+ private final List myParameterPatterns = new ArrayList();
+ private PatternValue myMethodPattern;
+
+ private Label myAssertLabel;
+
+ public InstrumentationAdapter(PatternInstrumenter instrumenter,
+ MethodVisitor methodvisitor,
+ Type[] argTypes,
+ Type returnType,
+ int access,
+ String name) {
+ super(Opcodes.ASM4, methodvisitor);
+ myInstrumenter = instrumenter;
+ myArgTypes = argTypes;
+ myReturnType = returnType;
+ myAccess = access;
+ myMethodName = name;
+ }
+
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+ final AnnotationVisitor annotationvisitor = mv.visitParameterAnnotation(parameter, desc, visible);
+
+ if (myArgTypes[parameter].getSort() == Type.OBJECT) {
+ final String annotationClassName = Type.getType(desc).getClassName();
+ if (myInstrumenter.acceptAnnotation(annotationClassName)) {
+ @Nullable final String patternString = myInstrumenter.getAnnotationPattern(annotationClassName);
+ final String[] strings = annotationClassName.split("\\.");
+ final PatternValue patternValue = new PatternValue(parameter, strings[strings.length - 1], patternString);
+ myParameterPatterns.add(patternValue);
+
+ // dig into the annotation and get the "value" element if pattern isn't present yet
+ return patternString == null ? new MyAnnotationVisitor(annotationvisitor, patternValue) : annotationvisitor;
+ }
+ }
+ return annotationvisitor;
+ }
+
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ final AnnotationVisitor annotationvisitor = mv.visitAnnotation(desc, visible);
+
+ if (myReturnType.getSort() == Type.OBJECT) {
+ final String annotationClassName = Type.getType(desc).getClassName();
+ if (myInstrumenter.acceptAnnotation(annotationClassName)) {
+ @Nullable final String pattern = myInstrumenter.getAnnotationPattern(annotationClassName);
+ final String[] strings = annotationClassName.split("\\.");
+ myMethodPattern = new PatternValue(-1, strings[strings.length - 1], pattern);
+
+ return pattern == null ? new MyAnnotationVisitor(annotationvisitor, myMethodPattern) : annotationvisitor;
+ }
+ }
+ return annotationvisitor;
+ }
+
+ public void visitCode() {
+ for (PatternValue parameter : myParameterPatterns) {
+ int j;
+ if ((myAccess & Opcodes.ACC_STATIC) == 0) {
+ // special case: ctor of non-static inner classes (see IDEA-10889)
+ if ("".equals(myMethodName)) {
+ // ACC_INTERFACE is (ab-)used to tunnel the information about the non-static inner class
+ j = (myInstrumenter.myIsNonStaticInnerClass) ? 1 + myArgTypes[0].getSize() // skip first (synthetic) "Outer.this" parameter
+ : 1;
+ }
+ else {
+ j = 1;
+ }
+ }
+ else {
+ j = 0;
+ }
+
+ for (int l = 0; l < parameter.index; l++) {
+ j += myArgTypes[l].getSize();
+ }
+
+ final Label checked = new Label();
+
+ addPatternTest(parameter.patternIndex, checked, j);
+
+ addPatternAssertion(MessageFormat.format("Argument {0} for @{1} parameter of {2}.{3} does not match pattern {4}", parameter.index,
+ parameter.annotation, myInstrumenter.myClassName, myMethodName, parameter.pattern), false);
+
+ mv.visitLabel(checked);
+ }
+
+ if (myMethodPattern != null) {
+ myAssertLabel = new Label();
+ }
+ }
+
+ public void visitInsn(int opcode) {
+ if (opcode == Opcodes.ARETURN && myAssertLabel != null) {
+ mv.visitJumpInsn(Opcodes.GOTO, myAssertLabel);
+ }
+ else {
+ mv.visitInsn(opcode);
+ }
+ }
+
+ public void visitMaxs(int maxStack, int maxLocals) {
+ if (myAssertLabel != null) {
+
+ // next index for synthetic variable that holds return value
+ final int var = maxLocals + 1;
+
+ mv.visitLabel(myAssertLabel);
+
+ mv.visitVarInsn(Opcodes.ASTORE, var);
+
+ final Label end = new Label();
+ addPatternTest(myMethodPattern.patternIndex, end, var);
+
+ addPatternAssertion(MessageFormat.format("Return value of method {0}.{1} annotated as @{2} does not match pattern {3}",
+ myInstrumenter.myClassName, myMethodName, myMethodPattern.annotation,
+ myMethodPattern.pattern), true);
+
+ mv.visitLabel(end);
+ mv.visitLocalVariable(RETURN_VALUE_NAME, PatternInstrumenter.JAVA_LANG_STRING, null, myAssertLabel, end, var);
+
+ mv.visitVarInsn(Opcodes.ALOAD, var);
+ mv.visitInsn(Opcodes.ARETURN);
+ }
+
+ super.visitMaxs(maxStack, maxLocals);
+ }
+
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ private void addPatternTest(int patternIndex, Label label, int varIndex) {
+ if (myInstrumenter.myInstrumentationType == InstrumentationType.ASSERT) {
+ mv.visitFieldInsn(Opcodes.GETSTATIC, myInstrumenter.myClassName, PatternInstrumenter.ASSERTIONS_DISABLED_NAME, "Z");
+ mv.visitJumpInsn(Opcodes.IFNE, label);
+ }
+
+ mv.visitVarInsn(Opcodes.ALOAD, varIndex);
+ mv.visitJumpInsn(Opcodes.IFNULL, label);
+
+ mv.visitFieldInsn(GETSTATIC, myInstrumenter.myClassName, PatternInstrumenter.PATTERN_CACHE_NAME,
+ "[Ljava/util/regex/Pattern;");
+ mv.visitIntInsn(BIPUSH, patternIndex);
+ mv.visitInsn(AALOAD);
+ mv.visitVarInsn(ALOAD, varIndex);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/regex/Pattern", "matcher", "(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/regex/Matcher", "matches", "()Z");
+
+ mv.visitJumpInsn(Opcodes.IFNE, label);
+ }
+
+ // TODO: add actual value to assertion message
+ private void addPatternAssertion(String message, boolean isMethod) {
+ if (myInstrumenter.myInstrumentationType == InstrumentationType.ASSERT) {
+ addThrow("java/lang/AssertionError", "(Ljava/lang/Object;)V", message);
+ }
+ else if (myInstrumenter.myInstrumentationType == InstrumentationType.EXCEPTION) {
+ if (isMethod) {
+ addThrow("java/lang/IllegalStateException", "(Ljava/lang/String;)V", message);
+ }
+ else {
+ addThrow("java/lang/IllegalArgumentException", "(Ljava/lang/String;)V", message);
+ }
+ }
+ myInstrumenter.myInstrumented = true;
+ }
+
+ private void addThrow(@NonNls String throwableClass, @NonNls String ctorSignature, String message) {
+ mv.visitTypeInsn(Opcodes.NEW, throwableClass);
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitLdcInsn(message);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, throwableClass, "", ctorSignature);
+ mv.visitInsn(Opcodes.ATHROW);
+ }
+
+ private static class MyAnnotationVisitor extends AnnotationVisitor {
+ private final AnnotationVisitor av;
+ private final PatternValue myPatternValue;
+
+ public MyAnnotationVisitor(AnnotationVisitor annotationvisitor, PatternValue v) {
+ super(Opcodes.ASM4);
+ av = annotationvisitor;
+ myPatternValue = v;
+ }
+
+ public void visit(@NonNls String name, Object value) {
+ av.visit(name, value);
+ if ("value".equals(name) && value instanceof String) {
+ myPatternValue.set((String)value);
+ }
+ }
+
+ public void visitEnum(String name, String desc, String value) {
+ av.visitEnum(name, desc, value);
+ }
+
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ return av.visitAnnotation(name, desc);
+ }
+
+ public AnnotationVisitor visitArray(String name) {
+ return av.visitArray(name);
+ }
+
+ public void visitEnd() {
+ av.visitEnd();
+ }
+ }
+
+ class PatternValue {
+ final int index;
+ final String annotation;
+ String pattern;
+ int patternIndex;
+
+ PatternValue(int index, String annotation, String pattern) {
+ this.index = index;
+ this.annotation = annotation;
+ if (pattern != null) set(pattern);
+ }
+
+ void set(String s) {
+ assert pattern == null;
+ patternIndex = myInstrumenter.addPattern(pattern = s);
+ }
+ }
+}
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/InstrumentationType.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/InstrumentationType.java
new file mode 100644
index 000000000000..c9c3966a9e92
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/InstrumentationType.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2000-2012 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.jps.intellilang.instrumentation;
+
+public enum InstrumentationType {
+ NONE, ASSERT, EXCEPTION
+}
+
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/PatternInstrumenter.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/PatternInstrumenter.java
new file mode 100644
index 000000000000..6d1bd2d249e3
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/PatternInstrumenter.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2000-2012 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.jps.intellilang.instrumentation;
+
+import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
+import com.intellij.openapi.util.Ref;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.asm4.*;
+import org.jetbrains.jps.intellilang.model.InstrumentationException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+
+class PatternInstrumenter extends ClassVisitor implements Opcodes {
+ @NonNls static final String PATTERN_CACHE_NAME = "$_PATTERN_CACHE_$";
+ @NonNls static final String ASSERTIONS_DISABLED_NAME = "$assertionsDisabled";
+ @NonNls static final String JAVA_LANG_STRING = "Ljava/lang/String;";
+ @NonNls static final String JAVA_UTIL_REGEX_PATTERN = "[Ljava/util/regex/Pattern;";
+
+ private boolean myHasAssertions;
+ private boolean myHasStaticInitializer;
+
+ private final LinkedHashSet myPatterns = new LinkedHashSet();
+
+ private final String myPatternAnnotationClassName;
+ final InstrumentationType myInstrumentationType;
+ private final InstrumentationClassFinder myClassFinder;
+ private final Map myAnnotationNameToPatternMap = new HashMap(); // can contain null values!
+ private final Set myProcessedAnnotations = new HashSet(); // checked annotation classes
+
+ String myClassName;
+ boolean myInstrumented;
+ boolean myIsNonStaticInnerClass;
+
+ public PatternInstrumenter(@NotNull String patternAnnotationClassName, ClassVisitor classvisitor,
+ InstrumentationType instrumentation,
+ InstrumentationClassFinder classFinder) {
+ super(Opcodes.ASM4, classvisitor);
+ myPatternAnnotationClassName = patternAnnotationClassName;
+
+ myInstrumentationType = instrumentation;
+ myClassFinder = classFinder;
+ // initial setup: null value means we should discover the pattern string 'inplace'
+ myAnnotationNameToPatternMap.put(patternAnnotationClassName, null);
+ myProcessedAnnotations.add(patternAnnotationClassName);
+ }
+
+ public boolean instrumented() {
+ return myInstrumented;
+ }
+
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ myClassName = name;
+ }
+
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ super.visitInnerClass(name, outerName, innerName, access);
+ if (myClassName.equals(name)) {
+ myIsNonStaticInnerClass = (access & ACC_STATIC) == 0;
+ }
+ }
+
+ public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) {
+ if (name.equals(ASSERTIONS_DISABLED_NAME)) {
+ myHasAssertions = true;
+ }
+ else if (name.equals(PATTERN_CACHE_NAME)) {
+ throw new InstrumentationException("Error: Processing an already instrumented class: " + myClassName + ". Please recompile the affected class(es) or rebuild the project.");
+ }
+
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ public void visitEnd() {
+ if (myInstrumented) {
+ addField(PATTERN_CACHE_NAME, ACC_PRIVATE + ACC_FINAL + ACC_STATIC + ACC_SYNTHETIC, JAVA_UTIL_REGEX_PATTERN);
+
+ if (myInstrumentationType == InstrumentationType.ASSERT) {
+ if (!myHasAssertions) {
+ addField(ASSERTIONS_DISABLED_NAME, ACC_FINAL + ACC_STATIC + ACC_SYNTHETIC, "Z");
+ }
+ }
+
+ if (!myHasStaticInitializer) {
+ createStaticInitializer();
+ }
+ }
+
+ super.visitEnd();
+ }
+
+ private void addField(String name, int modifiers, String type) {
+ final FieldVisitor fv = cv.visitField(modifiers, name, type, null, null);
+ fv.visitEnd();
+ }
+
+ private void createStaticInitializer() {
+ final MethodVisitor mv = cv.visitMethod(ACC_STATIC, "", "()V", null, null);
+ mv.visitCode();
+
+ patchStaticInitializer(mv);
+
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+
+ private void patchStaticInitializer(MethodVisitor mv) {
+ if (!myHasAssertions && myInstrumentationType == InstrumentationType.ASSERT) {
+ initAssertions(mv);
+ }
+
+ initPatterns(mv);
+ }
+
+ // verify pattern and add compiled pattern to static cache
+ private void initPatterns(MethodVisitor mv) {
+ mv.visitIntInsn(BIPUSH, myPatterns.size());
+ mv.visitTypeInsn(ANEWARRAY, "java/util/regex/Pattern");
+ mv.visitFieldInsn(PUTSTATIC, myClassName, PATTERN_CACHE_NAME, JAVA_UTIL_REGEX_PATTERN);
+
+ int i = 0;
+ for (String pattern : myPatterns) {
+ // check the pattern so we can rely on the pattern being valid at runtime
+ try {
+ Pattern.compile(pattern);
+ }
+ catch (Exception e) {
+ throw new InstrumentationException("Illegal Pattern: " + pattern, e);
+ }
+
+ mv.visitFieldInsn(GETSTATIC, myClassName, PATTERN_CACHE_NAME, JAVA_UTIL_REGEX_PATTERN);
+ mv.visitIntInsn(BIPUSH, i++);
+ mv.visitLdcInsn(pattern);
+ mv.visitMethodInsn(INVOKESTATIC, "java/util/regex/Pattern", "compile", "(Ljava/lang/String;)Ljava/util/regex/Pattern;");
+ mv.visitInsn(AASTORE);
+ }
+ }
+
+ // add assert startup code
+ private void initAssertions(MethodVisitor mv) {
+ mv.visitLdcInsn(Type.getType("L" + myClassName + ";"));
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z");
+ Label l0 = new Label();
+ mv.visitJumpInsn(IFNE, l0);
+ mv.visitInsn(ICONST_1);
+ Label l1 = new Label();
+ mv.visitJumpInsn(GOTO, l1);
+ mv.visitLabel(l0);
+ mv.visitInsn(ICONST_0);
+ mv.visitLabel(l1);
+ mv.visitFieldInsn(PUTSTATIC, myClassName, ASSERTIONS_DISABLED_NAME, "Z");
+ }
+
+ public MethodVisitor visitMethod(final int access, final String name, String desc, String signature, String[] exceptions) {
+ final MethodVisitor methodvisitor = cv.visitMethod(access, name, desc, signature, exceptions);
+
+ // patch static initializer
+ if ((access & ACC_STATIC) != 0 && name.equals("")) {
+ myHasStaticInitializer = true;
+
+ return new MethodVisitor(Opcodes.ASM4, methodvisitor) {
+ public void visitCode() {
+ super.visitCode();
+ patchStaticInitializer(mv);
+ }
+ };
+ }
+
+ final Type[] argTypes = Type.getArgumentTypes(desc);
+ final Type returnType = Type.getReturnType(desc);
+
+ // don't dig through the whole method if there's nothing to do in it
+ if (isStringType(returnType)) {
+ return new InstrumentationAdapter(this, methodvisitor, argTypes, returnType, access, name);
+ }
+ else {
+ for (Type type : argTypes) {
+ if (isStringType(type)) {
+ return new InstrumentationAdapter(this, methodvisitor, argTypes, returnType, access, name);
+ }
+ }
+ }
+
+ return methodvisitor;
+ }
+
+ private static boolean isStringType(Type type) {
+ return type.getSort() == Type.OBJECT && type.getDescriptor().equals(JAVA_LANG_STRING);
+ }
+
+ public int addPattern(String s) {
+ if (myPatterns.add(s)) {
+ return myPatterns.size() - 1;
+ }
+ return Arrays.asList(myPatterns.toArray()).indexOf(s);
+ }
+
+ public boolean acceptAnnotation(String annotationClassName) {
+ processAnnotation(annotationClassName);
+ return myAnnotationNameToPatternMap.containsKey(annotationClassName);
+ }
+
+ /**
+ * @param annotationClassname
+ * @return pattern string for 'alias' annotations, as specified in the 'base' annotation,
+ * otherwise null, (for the 'base' annotation class name null is returned as well)
+ */
+ @Nullable
+ public String getAnnotationPattern(String annotationClassName) {
+ processAnnotation(annotationClassName);
+ return myAnnotationNameToPatternMap.get(annotationClassName);
+ }
+
+ private void processAnnotation(String annotationClassName) {
+ if (!myProcessedAnnotations.add(annotationClassName)) {
+ return;
+ }
+ try {
+ final InputStream is = myClassFinder.getClassBytesAsStream(annotationClassName);
+ if (is != null) {
+ try {
+ final Ref patternString = new Ref(null);
+ // dig into annotation class and check if it is annotated with pattern annotation.
+ // if yes, load the pattern string from the pattern annotation and associate it with this annotation
+ final ClassVisitor visitor = new ClassVisitor(Opcodes.ASM4) {
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (patternString.get() != null || !myPatternAnnotationClassName.equals(Type.getType(desc).getClassName())) {
+ return null; // already found or is not pattern annotation
+ }
+ // dig into pattern annotation in order to discover the pattern string
+ return new AnnotationVisitor(Opcodes.ASM4) {
+ public void visit(@NonNls String name, Object value) {
+ if ("value".equals(name) && value instanceof String) {
+ patternString.set((String)value);
+ }
+ }
+ };
+ }
+ };
+ new ClassReader(is).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+
+ final String pattern = patternString.get();
+ if (pattern != null) {
+ myAnnotationNameToPatternMap.put(annotationClassName, pattern);
+ }
+ }
+ finally {
+ is.close();
+ }
+ }
+ }
+ catch (IOException ignored) {
+ // todo
+ }
+ }
+}
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/PatternValidatorBuilder.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/PatternValidatorBuilder.java
new file mode 100644
index 000000000000..5403315001c5
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/PatternValidatorBuilder.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2000-2012 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.jps.intellilang.instrumentation;
+
+import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.asm4.ClassReader;
+import org.jetbrains.asm4.ClassWriter;
+import org.jetbrains.jps.ModuleChunk;
+import org.jetbrains.jps.incremental.CompileContext;
+import org.jetbrains.jps.incremental.CompiledClass;
+import org.jetbrains.jps.incremental.instrumentation.BaseInstrumentingBuilder;
+import org.jetbrains.jps.incremental.messages.BuildMessage;
+import org.jetbrains.jps.incremental.messages.CompilerMessage;
+import org.jetbrains.jps.intellilang.model.InstrumentationException;
+import org.jetbrains.jps.intellilang.model.JpsIntelliLangConfiguration;
+import org.jetbrains.jps.intellilang.model.JpsIntelliLangExtensionService;
+import org.jetbrains.jps.javac.BinaryContent;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 11/25/12
+ */
+public class PatternValidatorBuilder extends BaseInstrumentingBuilder{
+ public PatternValidatorBuilder() {
+ }
+
+ @NotNull
+ @Override
+ public String getPresentableName() {
+ return "IntelliLang Pattern Validator";
+ }
+
+ @Override
+ protected boolean isEnabled(CompileContext context, ModuleChunk chunk) {
+ final JpsIntelliLangConfiguration config = JpsIntelliLangExtensionService.getInstance().getConfiguration(context.getProjectDescriptor().getModel().getGlobal());
+ return config.getInstrumentationType() != InstrumentationType.NONE;
+ }
+
+ @Override
+ protected boolean canInstrument(CompiledClass compiledClass, int classFileVersion) {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ protected BinaryContent instrument(CompileContext context, CompiledClass compiled, ClassReader reader, ClassWriter writer, InstrumentationClassFinder finder) {
+ final JpsIntelliLangConfiguration config =
+ JpsIntelliLangExtensionService.getInstance().getConfiguration(context.getProjectDescriptor().getModel().getGlobal());
+ final PatternInstrumenter instrumenter =
+ new PatternInstrumenter(config.getPatternAnnotationClass(), writer, config.getInstrumentationType(), finder);
+ try {
+ reader.accept(instrumenter, 0);
+ if (instrumenter.instrumented()) {
+ return new BinaryContent(writer.toByteArray());
+ }
+ }
+ catch (InstrumentationException e) {
+ context.processMessage(new CompilerMessage(getPresentableName(), BuildMessage.Kind.ERROR, e.getMessage()));
+ }
+ return null;
+ }
+
+ @Override
+ protected String getProgressMessage() {
+ return "Adding pattern assertions...";
+ }
+}
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/PatternValidatorBuilderService.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/PatternValidatorBuilderService.java
new file mode 100644
index 000000000000..39ba6dee9f69
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/instrumentation/PatternValidatorBuilderService.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2012 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.jps.intellilang.instrumentation;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.incremental.BuilderService;
+import org.jetbrains.jps.incremental.ModuleLevelBuilder;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 11/28/12
+ */
+public class PatternValidatorBuilderService extends BuilderService{
+ @NotNull
+ @Override
+ public List extends ModuleLevelBuilder> createModuleLevelBuilders() {
+ return Arrays.asList(new PatternValidatorBuilder());
+ }
+}
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/InstrumentationException.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/InstrumentationException.java
new file mode 100644
index 000000000000..f36f812d69bd
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/InstrumentationException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2012 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.jps.intellilang.model;
+
+public class InstrumentationException extends RuntimeException {
+ public InstrumentationException(String s) {
+ super(s);
+ }
+
+ public InstrumentationException(String s, Exception e) {
+ super(s, e);
+ }
+}
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/JpsIntelliLangConfiguration.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/JpsIntelliLangConfiguration.java
new file mode 100644
index 000000000000..02394570a07d
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/JpsIntelliLangConfiguration.java
@@ -0,0 +1,14 @@
+package org.jetbrains.jps.intellilang.model;
+
+import org.jetbrains.jps.intellilang.instrumentation.InstrumentationType;
+import org.jetbrains.jps.model.JpsElement;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 11/29/12
+ */
+public interface JpsIntelliLangConfiguration extends JpsElement {
+ String getPatternAnnotationClass();
+
+ InstrumentationType getInstrumentationType();
+}
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/JpsIntelliLangExtensionService.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/JpsIntelliLangExtensionService.java
new file mode 100644
index 000000000000..a59a05405772
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/JpsIntelliLangExtensionService.java
@@ -0,0 +1,20 @@
+package org.jetbrains.jps.intellilang.model;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.model.JpsGlobal;
+import org.jetbrains.jps.service.JpsServiceManager;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 11/29/12
+ */
+public abstract class JpsIntelliLangExtensionService {
+ public static JpsIntelliLangExtensionService getInstance() {
+ return JpsServiceManager.getInstance().getService(JpsIntelliLangExtensionService.class);
+ }
+
+ @NotNull
+ public abstract JpsIntelliLangConfiguration getConfiguration(@NotNull JpsGlobal project);
+
+ public abstract void setConfiguration(@NotNull JpsGlobal project, @NotNull JpsIntelliLangConfiguration extension);
+}
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangConfigurationImpl.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangConfigurationImpl.java
new file mode 100644
index 000000000000..3c74702d29ff
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangConfigurationImpl.java
@@ -0,0 +1,50 @@
+package org.jetbrains.jps.intellilang.model.impl;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.intellilang.instrumentation.InstrumentationType;
+import org.jetbrains.jps.intellilang.model.JpsIntelliLangConfiguration;
+import org.jetbrains.jps.model.JpsElementChildRole;
+import org.jetbrains.jps.model.ex.JpsElementBase;
+import org.jetbrains.jps.model.ex.JpsElementChildRoleBase;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 11/29/12
+ */
+public class JpsIntelliLangConfigurationImpl extends JpsElementBase implements
+ JpsIntelliLangConfiguration {
+ public static final JpsElementChildRole ROLE = JpsElementChildRoleBase.create("IntelliLang");
+
+ private String myPatternAnnotationClassName = "org.intellij.lang.annotations.Pattern";
+ private InstrumentationType myInstrumentationType = InstrumentationType.ASSERT;
+
+ @NotNull
+ @Override
+ public JpsIntelliLangConfigurationImpl createCopy() {
+ return new JpsIntelliLangConfigurationImpl();
+ }
+
+ @Override
+ public void applyChanges(@NotNull JpsIntelliLangConfigurationImpl modified) {
+ myPatternAnnotationClassName = modified.myPatternAnnotationClassName;
+ myInstrumentationType = modified.myInstrumentationType;
+ }
+
+ @Override
+ public String getPatternAnnotationClass() {
+ return myPatternAnnotationClassName;
+ }
+
+ @Override
+ public InstrumentationType getInstrumentationType() {
+ return myInstrumentationType;
+ }
+
+ public void setPatternAnnotationClassName(String patternAnnotationClassName) {
+ myPatternAnnotationClassName = patternAnnotationClassName;
+ }
+
+ public void setInstrumentationType(InstrumentationType instrumentationType) {
+ myInstrumentationType = instrumentationType;
+ }
+}
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangConfigurationSerializer.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangConfigurationSerializer.java
new file mode 100644
index 000000000000..385d0937e9ff
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangConfigurationSerializer.java
@@ -0,0 +1,56 @@
+package org.jetbrains.jps.intellilang.model.impl;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.JDOMExternalizerUtil;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.intellilang.instrumentation.InstrumentationType;
+import org.jetbrains.jps.intellilang.model.JpsIntelliLangExtensionService;
+import org.jetbrains.jps.model.JpsGlobal;
+import org.jetbrains.jps.model.serialization.JpsGlobalExtensionSerializer;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 11/29/12
+ */
+public class JpsIntelliLangConfigurationSerializer extends JpsGlobalExtensionSerializer {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.intellilang.model.impl.JpsIntelliLangConfigurationSerializer");
+ private static final String INSTRUMENTATION_TYPE_NAME = "INSTRUMENTATION";
+ private static final String PATTERN_ANNOTATION_NAME = "PATTERN_ANNOTATION";
+
+ public JpsIntelliLangConfigurationSerializer() {
+ super("IntelliLang.xml", "LanguageInjectionConfiguration");
+ }
+
+ @Override
+ public void loadExtension(@NotNull JpsGlobal global, @NotNull Element componentTag) {
+ final JpsIntelliLangConfigurationImpl configuration = new JpsIntelliLangConfigurationImpl();
+
+ final String annotationName = JDOMExternalizerUtil.readField(componentTag, PATTERN_ANNOTATION_NAME);
+ if (annotationName != null) {
+ configuration.setPatternAnnotationClassName(annotationName);
+ }
+
+ final String instrumentationType = JDOMExternalizerUtil.readField(componentTag, INSTRUMENTATION_TYPE_NAME);
+ if (instrumentationType != null) {
+ try {
+ final InstrumentationType type = InstrumentationType.valueOf(instrumentationType);
+ configuration.setInstrumentationType(type);
+ }
+ catch (IllegalArgumentException ignored) {
+ LOG.info(ignored);
+ }
+ }
+
+ JpsIntelliLangExtensionService.getInstance().setConfiguration(global, configuration);
+ }
+
+ @Override
+ public void loadExtensionWithDefaultSettings(@NotNull JpsGlobal global) {
+ JpsIntelliLangExtensionService.getInstance().setConfiguration(global, new JpsIntelliLangConfigurationImpl());
+ }
+
+ @Override
+ public void saveExtension(@NotNull JpsGlobal global, @NotNull Element componentTag) {
+ }
+}
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangExtensionServiceImpl.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangExtensionServiceImpl.java
new file mode 100644
index 000000000000..1fa016afeb12
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangExtensionServiceImpl.java
@@ -0,0 +1,23 @@
+package org.jetbrains.jps.intellilang.model.impl;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.intellilang.model.JpsIntelliLangConfiguration;
+import org.jetbrains.jps.intellilang.model.JpsIntelliLangExtensionService;
+import org.jetbrains.jps.model.JpsGlobal;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 11/29/12
+ */
+public class JpsIntelliLangExtensionServiceImpl extends JpsIntelliLangExtensionService {
+ @NotNull
+ @Override
+ public JpsIntelliLangConfiguration getConfiguration(@NotNull JpsGlobal global) {
+ return global.getContainer().getChild(JpsIntelliLangConfigurationImpl.ROLE);
+ }
+
+ @Override
+ public void setConfiguration(@NotNull JpsGlobal global, @NotNull JpsIntelliLangConfiguration config) {
+ global.getContainer().setChild(JpsIntelliLangConfigurationImpl.ROLE, config);
+ }
+}
diff --git a/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangModelSerializerExtension.java b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangModelSerializerExtension.java
new file mode 100644
index 000000000000..609939c758a7
--- /dev/null
+++ b/plugins/IntelliLang/intellilang-jps-plugin/src/org/jetbrains/jps/intellilang/model/impl/JpsIntelliLangModelSerializerExtension.java
@@ -0,0 +1,20 @@
+package org.jetbrains.jps.intellilang.model.impl;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.model.serialization.JpsGlobalExtensionSerializer;
+import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 11/29/12
+ */
+public class JpsIntelliLangModelSerializerExtension extends JpsModelSerializerExtension{
+ @NotNull
+ @Override
+ public List extends JpsGlobalExtensionSerializer> getGlobalExtensionSerializers() {
+ return Arrays.asList(new JpsIntelliLangConfigurationSerializer());
+ }
+}
diff --git a/plugins/IntelliLang/src/META-INF/plugin.xml b/plugins/IntelliLang/src/META-INF/plugin.xml
index 96c446486839..01845b44d776 100644
--- a/plugins/IntelliLang/src/META-INF/plugin.xml
+++ b/plugins/IntelliLang/src/META-INF/plugin.xml
@@ -43,5 +43,7 @@
serviceImplementation="org.intellij.plugins.intelliLang.Configuration$App"/>
+
+