intellilang pattern validation for external build

This commit is contained in:
Eugene Zhuravlev
2012-11-29 16:12:32 +01:00
parent 7d11279420
commit 52cb602e4c
20 changed files with 927 additions and 14 deletions
+1
View File
@@ -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" />
+3
View File
@@ -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")
}
}
}
}
@@ -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>
@@ -0,0 +1 @@
org.jetbrains.jps.intellilang.instrumentation.PatternValidatorBuilderService
@@ -0,0 +1 @@
org.jetbrains.jps.intellilang.model.impl.JpsIntelliLangExtensionServiceImpl
@@ -0,0 +1 @@
org.jetbrains.jps.intellilang.model.impl.JpsIntelliLangModelSerializerExtension
@@ -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);
}
}
}
@@ -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
}
@@ -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
}
}
}
@@ -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...";
}
}
@@ -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());
}
}
@@ -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);
}
}
@@ -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();
}
@@ -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);
}
@@ -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;
}
}
@@ -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) {
}
}
@@ -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);
}
}
@@ -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>