mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
intellilang pattern validation for external build
This commit is contained in:
Generated
+1
@@ -63,6 +63,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/platform/indexing-api/indexing-api.iml" filepath="$PROJECT_DIR$/platform/indexing-api/indexing-api.iml" group="platform" />
|
||||
<module fileurl="file://$PROJECT_DIR$/platform/indexing-impl/indexing-impl.iml" filepath="$PROJECT_DIR$/platform/indexing-impl/indexing-impl.iml" group="platform" />
|
||||
<module fileurl="file://$PROJECT_DIR$/java/compiler/instrumentation-util/instrumentation-util.iml" filepath="$PROJECT_DIR$/java/compiler/instrumentation-util/instrumentation-util.iml" group="java/compiler" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/IntelliLang/intellilang-jps-plugin/intellilang-jps-plugin.iml" filepath="$PROJECT_DIR$/plugins/IntelliLang/intellilang-jps-plugin/intellilang-jps-plugin.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/java-i18n/java-i18n.iml" filepath="$PROJECT_DIR$/plugins/java-i18n/java-i18n.iml" group="plugins" />
|
||||
<module fileurl="file://$PROJECT_DIR$/java/java-impl/java-impl.iml" filepath="$PROJECT_DIR$/java/java-impl/java-impl.iml" group="java" />
|
||||
<module fileurl="file://$PROJECT_DIR$/java/java-indexing-api/java-indexing-api.iml" filepath="$PROJECT_DIR$/java/java-indexing-api/java-indexing-api.iml" group="java" />
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-14
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="jps-builders" />
|
||||
<orderEntry type="module" module-name="jps-model-api" />
|
||||
<orderEntry type="module" module-name="instrumentation-util" />
|
||||
<orderEntry type="module" module-name="jps-model-serialization" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.jps.intellilang.instrumentation.PatternValidatorBuilderService
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.jps.intellilang.model.impl.JpsIntelliLangExtensionServiceImpl
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.jps.intellilang.model.impl.JpsIntelliLangModelSerializerExtension
|
||||
+259
@@ -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<PatternValue> myParameterPatterns = new ArrayList<PatternValue>();
|
||||
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 ("<init>".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, "<init>", 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -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
|
||||
}
|
||||
|
||||
+276
@@ -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<String> myPatterns = new LinkedHashSet<String>();
|
||||
|
||||
private final String myPatternAnnotationClassName;
|
||||
final InstrumentationType myInstrumentationType;
|
||||
private final InstrumentationClassFinder myClassFinder;
|
||||
private final Map<String, String> myAnnotationNameToPatternMap = new HashMap<String, String>(); // can contain null values!
|
||||
private final Set<String> myProcessedAnnotations = new HashSet<String>(); // 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, "<clinit>", "()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("<clinit>")) {
|
||||
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<String> patternString = new Ref<String>(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
|
||||
}
|
||||
}
|
||||
}
|
||||
+82
@@ -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...";
|
||||
}
|
||||
}
|
||||
+35
@@ -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());
|
||||
}
|
||||
}
|
||||
+26
@@ -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);
|
||||
}
|
||||
}
|
||||
+14
@@ -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();
|
||||
}
|
||||
+20
@@ -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);
|
||||
}
|
||||
+50
@@ -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<JpsIntelliLangConfigurationImpl> implements
|
||||
JpsIntelliLangConfiguration {
|
||||
public static final JpsElementChildRole<JpsIntelliLangConfiguration> 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;
|
||||
}
|
||||
}
|
||||
+56
@@ -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) {
|
||||
}
|
||||
}
|
||||
+23
@@ -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);
|
||||
}
|
||||
}
|
||||
+20
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -43,5 +43,7 @@
|
||||
serviceImplementation="org.intellij.plugins.intelliLang.Configuration$App"/>
|
||||
<projectService serviceInterface="org.intellij.plugins.intelliLang.Configuration"
|
||||
serviceImplementation="org.intellij.plugins.intelliLang.Configuration$Prj"/>
|
||||
<compileServer.plugin classpath="intellilang-jps-plugin.jar"/>
|
||||
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
Reference in New Issue
Block a user