diff --git a/.gitignore b/.gitignore
index 14a1be379c..51bcdf915f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,4 +62,7 @@ integration/hibernate/.classpath
integration/hibernate/.factorypath
integration/hibernate/target/
/core/impl/nbactions.xml
-*/**/dependency-reduced-pom.xml
\ No newline at end of file
+*/**/dependency-reduced-pom.xml
+out/
+/plugin/intellij/build/
+.gradle/
diff --git a/plugin/antlr/pom.xml b/plugin/antlr/pom.xml
new file mode 100644
index 0000000000..18bc622bfe
--- /dev/null
+++ b/plugin/antlr/pom.xml
@@ -0,0 +1,79 @@
+
+
+
+
+ 4.0.0
+
+
+ com.blazebit
+ blaze-persistence-plugin
+ 1.4.0-SNAPSHOT
+ ../pom.xml
+
+
+ blaze-persistence-plugin-antlr
+ jar
+
+ Blazebit Persistence Plugin Antlr
+
+
+ com.blazebit.persistence.plugin.antlr
+
+
+
+
+
+
+ org.antlr
+ antlr4-intellij-adaptor
+ 0.1
+
+
+ org.antlr
+ antlr4-runtime
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+
+ package
+
+ shade
+
+
+ true
+
+
+ org.antlr.v4.runtime
+ com.blazebit.persistence.parser.antlr
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin/intellij/build.gradle b/plugin/intellij/build.gradle
new file mode 100644
index 0000000000..96d77e09cd
--- /dev/null
+++ b/plugin/intellij/build.gradle
@@ -0,0 +1,55 @@
+plugins {
+ id 'java'
+ id 'org.jetbrains.intellij' version '0.4.10'
+}
+
+apply plugin: 'idea'
+apply plugin: 'java'
+apply plugin: 'org.jetbrains.intellij'
+
+sourceCompatibility = 1.8
+group = 'com.blazebit'
+version = '1.4.0-SNAPSHOT'
+def intellijVersion = '192.5728.98' // 2019.2
+
+repositories {
+ mavenLocal()
+ maven { url 'target/dependencies' }
+ maven { url "https://www.jetbrains.com/intellij-repository/releases" }
+ maven { url "https://jetbrains.bintray.com/intellij-third-party-dependencies" }
+}
+
+dependencies {
+ compile "$group:blaze-persistence-core-parser:$version"
+ compile "$group:blaze-persistence-plugin-antlr:$version"
+ compileOnly "com.jetbrains.intellij.java:java:$intellijVersion"
+ compileOnly "com.jetbrains.intellij.platform:lang-injection:$intellijVersion"
+}
+
+intellij {
+ version System.getenv().getOrDefault('IDEA_VERSION', ideaVersion)
+ type ideaType
+ downloadSources Boolean.valueOf(sources)
+ sameSinceUntilBuild Boolean.valueOf(isEAP)
+ alternativeIdePath idePath
+ updateSinceUntilBuild false
+ pluginName 'Blaze-Persistence Support'
+ if ( !(version.startsWith('2018') || version.startsWith('2019.1'))) {
+ plugins 'java'
+ }
+}
+
+tasks.register('javadocJar', Jar) {
+ archiveClassifier = 'javadoc'
+ from javadoc.destinationDir
+ dependsOn javadoc
+}
+
+tasks.register('sourcesJar', Jar) {
+ archiveClassifier = 'sources'
+ from sourceSets.main.allSource
+ dependsOn classes
+}
+
+assemble.dependsOn javadocJar
+assemble.dependsOn sourcesJar
\ No newline at end of file
diff --git a/plugin/intellij/gradle.properties b/plugin/intellij/gradle.properties
new file mode 100644
index 0000000000..ec40248138
--- /dev/null
+++ b/plugin/intellij/gradle.properties
@@ -0,0 +1,16 @@
+ideaVersion = 2019.2
+ideaType = IC
+sources = true
+isEAP = false
+runGenerators = true
+
+pluginGroup = com.blazebit
+pluginName = blaze-persistence-plugin-intellij
+pluginVersion = 1.4.0-SNAPSHOT
+
+publishPluginId =
+publishUsername =
+publishPassword =
+publishChannel =
+
+idePath =
\ No newline at end of file
diff --git a/plugin/intellij/gradle/wrapper/gradle-wrapper.jar b/plugin/intellij/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..5c2d1cf016
Binary files /dev/null and b/plugin/intellij/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/plugin/intellij/gradle/wrapper/gradle-wrapper.properties b/plugin/intellij/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..5e3de94fbd
--- /dev/null
+++ b/plugin/intellij/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
\ No newline at end of file
diff --git a/plugin/intellij/gradlew b/plugin/intellij/gradlew
new file mode 100644
index 0000000000..507670c705
--- /dev/null
+++ b/plugin/intellij/gradlew
@@ -0,0 +1,188 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+# https://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.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
\ No newline at end of file
diff --git a/plugin/intellij/gradlew.bat b/plugin/intellij/gradlew.bat
new file mode 100644
index 0000000000..15e3d8472e
--- /dev/null
+++ b/plugin/intellij/gradlew.bat
@@ -0,0 +1,100 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
\ No newline at end of file
diff --git a/plugin/intellij/pom.xml b/plugin/intellij/pom.xml
new file mode 100644
index 0000000000..3fee41f125
--- /dev/null
+++ b/plugin/intellij/pom.xml
@@ -0,0 +1,187 @@
+
+
+
+
+ 4.0.0
+
+
+ com.blazebit
+ blaze-persistence-plugin
+ 1.4.0-SNAPSHOT
+ ../pom.xml
+
+
+ blaze-persistence-plugin-intellij
+ jar
+
+ Blazebit Persistence Plugin IntelliJ
+
+
+ ./gradlew
+ com.blazebit.persistence.plugin.intellij
+
+
+
+
+
+ maven-compiler-plugin
+
+
+ default-compile
+ NONE
+
+
+
+
+ maven-jar-plugin
+
+
+ default-jar
+ NONE
+
+
+ attach-sources-no-fork
+ NONE
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${version.checkstyle.plugin}
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-dependencies
+ generate-resources
+
+ copy-dependencies
+
+ false
+
+ true
+ true
+ true
+ true
+ ${project.build.directory}/dependencies
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ gradle
+ prepare-package
+
+ ${gradle.executable}
+
+ clean
+ assemble
+ -Pgroup=${project.groupId}
+ -Pversion=${project.version}
+ -S
+
+
+
+ exec
+
+
+
+
+
+
+ maven-resources-plugin
+
+
+ copy-gradle-jars
+ package
+
+ copy-resources
+
+
+
+ ${basedir}/target
+
+
+ build/libs/
+
+ ${project.artifactId}-${project.version}.jar
+ ${project.artifactId}-${project.version}-javadoc.jar
+ ${project.artifactId}-${project.version}-sources.jar
+
+
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ attach-artifacts
+ package
+
+ attach-artifact
+
+
+
+
+ target/${project.artifactId}-${project.version}-javadoc.jar
+ jar
+ javadoc
+
+
+ target/${project.artifactId}-${project.version}-sources.jar
+ jar
+ sources
+
+
+
+
+
+
+
+
+
+
+
+ windows
+
+
+ windows
+
+
+
+ gradlew.bat
+
+
+
+
\ No newline at end of file
diff --git a/plugin/intellij/settings.gradle b/plugin/intellij/settings.gradle
new file mode 100644
index 0000000000..4235304b1e
--- /dev/null
+++ b/plugin/intellij/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'blaze-persistence-plugin-intellij'
\ No newline at end of file
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/EditEntityViewDialog.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/EditEntityViewDialog.java
new file mode 100644
index 0000000000..1c6ef8a0cc
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/EditEntityViewDialog.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.psi.JavaCodeFragmentFactory;
+import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiAnnotationMemberValue;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiClassObjectAccessExpression;
+import com.intellij.psi.PsiClassType;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiExpression;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiJavaCodeReferenceCodeFragment;
+import com.intellij.psi.PsiLiteralExpression;
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.PsiModifier;
+import com.intellij.psi.PsiReferenceExpression;
+import com.intellij.ui.EditorTextField;
+import com.intellij.ui.JavaReferenceEditorUtil;
+import com.intellij.uiDesigner.core.GridConstraints;
+import com.intellij.uiDesigner.core.GridLayoutManager;
+import com.intellij.util.TextFieldCompletionProvider;
+import com.intellij.util.textCompletion.DefaultTextCompletionValueDescriptor;
+import com.intellij.util.textCompletion.TextFieldWithCompletion;
+import com.intellij.util.textCompletion.ValuesCompletionProvider;
+import com.siyeh.ig.psiutils.ExpressionUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class EditEntityViewDialog extends DialogWrapper {
+
+ private final JPanel dialogPanel = new JPanel(new BorderLayout());
+
+ public EditEntityViewDialog(@NotNull Project project, PsiClass element) {
+ super(project, true);
+ init();
+ setTitle("Edit Entity View");
+
+ PsiAnnotation entityViewAnnotation = element.getAnnotation("com.blazebit.persistence.view.EntityView");
+ PsiClass entityClass = ((PsiClassType) ExpressionUtils.computeConstantExpression((PsiExpression) entityViewAnnotation.findAttributeValue("value"), true)).resolve();
+ PsiMethod[] methods = element.getMethods();
+ List mappingEntries = new ArrayList<>(methods.length);
+ List methodNames = new ArrayList<>(methods.length);
+
+ for (PsiMethod method : methods) {
+ methodNames.add(method.getName());
+ MappingEntry mappingEntry = MappingEntry.of(method);
+ if (mappingEntry != null) {
+ mappingEntries.add(mappingEntry);
+ }
+ }
+
+ ValuesCompletionProvider methodNameCompletionProvider = new ValuesCompletionProvider<>(new DefaultTextCompletionValueDescriptor.StringValueDescriptor(), methodNames);
+
+ PsiMethod[] entityMethods = entityClass.getAllMethods();
+ PsiField[] entityFields = entityClass.getAllFields();
+ List mappings = new ArrayList<>(entityMethods.length + entityFields.length);
+ for (PsiMethod entityMethod : entityMethods) {
+ if (!"java.lang.Object".equals(entityMethod.getContainingClass().getQualifiedName())) {
+ String attributeName = toAttributeName(entityMethod.getName());
+ if (attributeName != null) {
+ mappings.add(attributeName);
+ }
+ }
+ }
+
+ ValuesCompletionProvider mappingCompletionProvider = new ValuesCompletionProvider<>(new DefaultTextCompletionValueDescriptor.StringValueDescriptor(), mappings);
+
+ // ConfigPanel
+ {
+ JPanel configPanel = new JPanel(new GridLayoutManager(3, 2));
+ JLabel label = new JLabel("Entity class");
+ label.setPreferredSize(new Dimension(100, 100));
+
+ // TODO: only entity classes?
+ EditorTextField entityClassEditorTextField = new EditorTextField(JavaReferenceEditorUtil.createDocument(entityClass.getQualifiedName(), project, true), project, StdFileTypes.CLASS);
+ entityClassEditorTextField.setPreferredWidth(400);
+
+ GridConstraints gridConstraints = new GridConstraints();
+ gridConstraints.setRow(0);
+ gridConstraints.setColumn(0);
+ configPanel.add(label, gridConstraints);
+
+ gridConstraints = new GridConstraints();
+ gridConstraints.setRow(0);
+ gridConstraints.setColumn(1);
+ configPanel.add(entityClassEditorTextField, gridConstraints);
+
+ configPanel.setPreferredSize(new Dimension(600, 100));
+ dialogPanel.add(configPanel, BorderLayout.NORTH);
+ }
+
+ // Mapping Panel
+ {
+ int rowCount = mappingEntries.size() + 1;
+ int columnCount = 3;
+ JPanel mappingPanel = new JPanel(new GridLayoutManager(rowCount, columnCount));
+
+ {
+ JLabel attributeLabel = new JLabel("Attribute");
+ attributeLabel.setPreferredSize(new Dimension(100, 100));
+
+ JLabel methodNameLabel = new JLabel("Method");
+ methodNameLabel.setPreferredSize(new Dimension(100, 100));
+
+ JLabel mappingLabel = new JLabel("Mapping");
+ mappingLabel.setPreferredSize(new Dimension(100, 100));
+
+ GridConstraints gridConstraints = new GridConstraints();
+ gridConstraints.setRow(0);
+ gridConstraints.setColumn(0);
+ mappingPanel.add(attributeLabel, gridConstraints);
+
+ gridConstraints = new GridConstraints();
+ gridConstraints.setRow(0);
+ gridConstraints.setColumn(1);
+ mappingPanel.add(methodNameLabel, gridConstraints);
+
+ gridConstraints = new GridConstraints();
+ gridConstraints.setRow(0);
+ gridConstraints.setColumn(2);
+ mappingPanel.add(mappingLabel, gridConstraints);
+ }
+
+ int row = 1;
+ for (MappingEntry mappingEntry : mappingEntries) {
+ EditorTextField attributeNameEditorTextField = new EditorTextField(project, StdFileTypes.PLAIN_TEXT);
+ attributeNameEditorTextField.setPreferredWidth(200);
+ attributeNameEditorTextField.setText(mappingEntry.attributeName);
+
+ EditorTextField methodEditorTextField = new TextFieldWithCompletion(project, methodNameCompletionProvider, mappingEntry.methodName, true, true, true);
+ methodEditorTextField.setPreferredWidth(200);
+
+ EditorTextField mappingEditorTextField = new TextFieldWithCompletion(project, mappingCompletionProvider, mappingEntry.mapping, true, true, true);
+ mappingEditorTextField.setPreferredWidth(200);
+
+ GridConstraints gridConstraints = new GridConstraints();
+ gridConstraints.setRow(row);
+ gridConstraints.setColumn(0);
+ mappingPanel.add(attributeNameEditorTextField, gridConstraints);
+
+ gridConstraints = new GridConstraints();
+ gridConstraints.setRow(row);
+ gridConstraints.setColumn(1);
+ mappingPanel.add(methodEditorTextField, gridConstraints);
+
+ gridConstraints = new GridConstraints();
+ gridConstraints.setRow(row);
+ gridConstraints.setColumn(2);
+ mappingPanel.add(mappingEditorTextField, gridConstraints);
+ row++;
+ }
+
+
+ mappingPanel.setPreferredSize(new Dimension(600, 400));
+ dialogPanel.add(mappingPanel, BorderLayout.CENTER);
+ }
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCenterPanel() {
+ return dialogPanel;
+ }
+
+ public static String toAttributeName(String methodName) {
+ StringBuilder sb = new StringBuilder();
+ if (methodName.startsWith("is")) {
+ sb.append(methodName, 2, methodName.length());
+ } else if (methodName.startsWith("get")) {
+ sb.append(methodName, 3, methodName.length());
+ } else {
+ return null;
+ }
+ sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
+ return sb.toString();
+ }
+
+ private static class MappingEntry {
+ private String attributeName;
+ private String methodName;
+ private PsiMethod method;
+ private String mapping;
+
+ public MappingEntry(String attributeName, String methodName, PsiMethod method, String mapping) {
+ this.attributeName = attributeName;
+ this.methodName = methodName;
+ this.method = method;
+ this.mapping = mapping;
+ }
+
+ public static MappingEntry of(PsiMethod method) {
+ if (!method.getModifierList().hasModifierProperty(PsiModifier.ABSTRACT)) {
+ return null;
+ }
+ String attributeName = toAttributeName(method.getName());
+ if (attributeName == null) {
+ return null;
+ }
+
+ String mapping = attributeName;
+ PsiAnnotation mappingAnnotation = method.getAnnotation("com.blazebit.persistence.view.Mapping");
+ if (mappingAnnotation != null) {
+ PsiAnnotationMemberValue value = mappingAnnotation.findAttributeValue("value");
+ mapping = (String) ExpressionUtils.computeConstantExpression((PsiExpression) value, true);
+ }
+
+ return new MappingEntry(attributeName, method.getName(), method, mapping);
+ }
+
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/EntityViewUtils.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/EntityViewUtils.java
new file mode 100644
index 0000000000..000bde686f
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/EntityViewUtils.java
@@ -0,0 +1,22 @@
+package com.blazebit.persistence.plugin.intellij;
+
+import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiClassType;
+import com.intellij.psi.PsiExpression;
+import com.siyeh.ig.psiutils.ExpressionUtils;
+
+public final class EntityViewUtils {
+
+ private EntityViewUtils() {
+ }
+
+ public static PsiClass getEntityViewEntityClass(PsiClass entityViewClass) {
+ PsiAnnotation entityViewAnnotation = entityViewClass.getAnnotation("com.blazebit.persistence.view.EntityView");
+ if (entityViewAnnotation == null) {
+ return null;
+ }
+
+ return ((PsiClassType) ExpressionUtils.computeConstantExpression((PsiExpression) entityViewAnnotation.findAttributeValue("value"), true)).resolve();
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/GenerateEntityViewAttributesAction.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/GenerateEntityViewAttributesAction.java
new file mode 100644
index 0000000000..efdb795b42
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/GenerateEntityViewAttributesAction.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.intellij.codeInsight.generation.actions.BaseGenerateAction;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiModifier;
+
+public class GenerateEntityViewAttributesAction extends BaseGenerateAction {
+
+ public GenerateEntityViewAttributesAction() {
+ super(new GenerateEntityViewAttributesActionHandler());
+ }
+
+ @Override
+ protected boolean isValidForClass(PsiClass targetClass) {
+ return targetClass.isInterface() || targetClass.getModifierList().hasModifierProperty(PsiModifier.ABSTRACT);
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/GenerateEntityViewAttributesActionHandler.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/GenerateEntityViewAttributesActionHandler.java
new file mode 100644
index 0000000000..a7dca5d029
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/GenerateEntityViewAttributesActionHandler.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.intellij.codeInsight.CodeInsightActionHandler;
+import com.intellij.codeInsight.generation.ClassMember;
+import com.intellij.codeInsight.generation.EncapsulatableClassMember;
+import com.intellij.codeInsight.generation.GenerateMembersHandlerBase;
+import com.intellij.codeInsight.generation.GenerateMembersUtil;
+import com.intellij.codeInsight.generation.GenerationInfo;
+import com.intellij.codeInsight.generation.MemberChooserObject;
+import com.intellij.codeInsight.generation.OverrideImplementUtil;
+import com.intellij.codeInsight.generation.PsiDocCommentOwnerMemberChooserObject;
+import com.intellij.codeInsight.generation.PsiElementClassMember;
+import com.intellij.codeInsight.generation.PsiGenerationInfo;
+import com.intellij.codeInsight.generation.TemplateGenerationInfo;
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.codeInspection.ex.GlobalInspectionContextBase;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.util.MemberChooser;
+import com.intellij.lang.ContextAwareActionHandler;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorModificationUtil;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.ThrowableComputable;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiClassType;
+import com.intellij.psi.PsiDocCommentOwner;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiExpression;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiMember;
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.PsiModifier;
+import com.intellij.psi.PsiType;
+import com.intellij.psi.util.PsiFormatUtil;
+import com.intellij.ui.SimpleColoredComponent;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.ui.speedSearch.SpeedSearchUtil;
+import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.containers.ContainerUtil;
+import com.siyeh.ig.psiutils.ExpressionUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.java.generate.exception.GenerateCodeException;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static com.blazebit.persistence.plugin.intellij.EditEntityViewDialog.toAttributeName;
+
+public class GenerateEntityViewAttributesActionHandler implements CodeInsightActionHandler, ContextAwareActionHandler {
+
+ private final String myChooserTitle;
+ protected boolean myToCopyJavaDoc;
+
+ public GenerateEntityViewAttributesActionHandler() {
+ this.myChooserTitle = "Generate Entity View attributes";
+ }
+
+ @Nullable
+ protected ClassMember[] chooseOriginalMembers(PsiClass aClass, Project project) {
+ ClassMember[] allMembers = getAllOriginalMembers(aClass);
+ return chooseMembers(allMembers, false, false, project, null);
+ }
+
+ @Nullable
+ protected ClassMember[] chooseOriginalMembers(PsiClass aClass, Project project, Editor editor) {
+ return chooseOriginalMembers(aClass, project);
+ }
+
+ @Nullable
+ protected ClassMember[] chooseMembers(ClassMember[] members,
+ boolean allowEmptySelection,
+ boolean copyJavadocCheckbox,
+ Project project,
+ @Nullable Editor editor) {
+ MemberChooser chooser = createMembersChooser(members, allowEmptySelection, copyJavadocCheckbox, project);
+ if (editor != null) {
+ final int offset = editor.getCaretModel().getOffset();
+
+ ClassMember preselection = null;
+ for (ClassMember member : members) {
+ if (member instanceof PsiElementClassMember) {
+ final PsiDocCommentOwner owner = ((PsiElementClassMember)member).getElement();
+ if (owner != null) {
+ final TextRange textRange = owner.getTextRange();
+ if (textRange != null && textRange.contains(offset)) {
+ preselection = member;
+ break;
+ }
+ }
+ }
+ }
+ if (preselection != null) {
+ chooser.selectElements(new ClassMember[]{preselection});
+ }
+ }
+
+ chooser.show();
+ myToCopyJavaDoc = chooser.isCopyJavadoc();
+ final List list = chooser.getSelectedElements();
+ return list == null ? null : list.toArray(ClassMember.EMPTY_ARRAY);
+ }
+
+ @Nullable
+ protected JComponent getHeaderPanel(Project project) {
+ return null;
+ }
+
+ @Nullable
+ protected JComponent[] getOptionControls() {
+ return null;
+ }
+
+ protected MemberChooser createMembersChooser(ClassMember[] members,
+ boolean allowEmptySelection,
+ boolean copyJavadocCheckbox,
+ Project project) {
+ MemberChooser chooser = new MemberChooser(members, allowEmptySelection, true, project, getHeaderPanel(project), getOptionControls()) {
+ @Nullable
+ @Override
+ protected String getHelpId() {
+ return GenerateEntityViewAttributesActionHandler.this.getHelpId();
+ }
+ };
+ chooser.setTitle(myChooserTitle);
+ chooser.setCopyJavadocVisible(copyJavadocCheckbox);
+ return chooser;
+ }
+
+ protected String getHelpId() {
+ return null;
+ }
+
+ protected boolean hasMembers(@NotNull PsiClass aClass) {
+ return true;
+ }
+
+ @Override
+ public boolean isAvailableForQuickList(@NotNull Editor editor, @NotNull PsiFile file, @NotNull DataContext dataContext) {
+ PsiClass aClass = OverrideImplementUtil.getContextClass(file.getProject(), editor, file, true);
+ return aClass != null && this.hasMembers(aClass);
+ }
+
+ public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
+ if (EditorModificationUtil.checkModificationAllowed(editor)) {
+ if (FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
+ PsiClass aClass = OverrideImplementUtil.getContextClass(project, editor, file, true);
+ if (aClass != null && aClass.isInterface() || aClass.getModifierList().hasModifierProperty(PsiModifier.ABSTRACT)) {
+
+ try {
+ ClassMember[] members = this.chooseOriginalMembers(aClass, project, editor);
+ if (members == null) {
+ return;
+ }
+
+ CommandProcessor.getInstance().executeCommand(project, () -> {
+ int offset = editor.getCaretModel().getOffset();
+
+ try {
+ this.doGenerate(project, editor, aClass, members);
+ } catch (GenerateCodeException ex) {
+ String message = ex.getMessage();
+ ApplicationManager.getApplication().invokeLater(() -> {
+ if (!editor.isDisposed()) {
+ editor.getCaretModel().moveToOffset(offset);
+ HintManager.getInstance().showErrorHint(editor, message);
+ }
+
+ }, project.getDisposed());
+ }
+
+ }, (String)null, (Object)null);
+ } finally {
+ this.cleanup();
+ }
+
+ }
+ }
+ }
+ }
+
+ protected ClassMember[] getAllOriginalMembers(PsiClass entityViewClass) {
+ PsiClass entityClass = EntityViewUtils.getEntityViewEntityClass(entityViewClass);
+
+ PsiMethod[] entityMethods = entityClass.getAllMethods();
+ PsiField[] entityFields = entityClass.getAllFields();
+ List classMembers = new ArrayList<>(entityMethods.length + entityFields.length);
+ for (PsiMethod entityMethod : entityMethods) {
+ if (!"java.lang.Object".equals(entityMethod.getContainingClass().getQualifiedName())) {
+ String attributeName = toAttributeName(entityMethod.getName());
+ if (attributeName != null) {
+ classMembers.add(new EntityAttribute(entityViewClass, entityClass, attributeName, entityMethod.getReturnType()));
+ }
+ }
+ }
+ return classMembers.toArray(new ClassMember[classMembers.size()]);
+ }
+
+ protected List generateMemberPrototypes(PsiClass psiClass, ClassMember[] classMembers) throws IncorrectOperationException {
+ if (classMembers != null && classMembers.length != 0) {
+ List generationInfos = new ArrayList<>(classMembers.length);
+ for (int i = 0; i < classMembers.length; i++) {
+ generationInfos.add(((EntityAttribute) classMembers[i]).generateGetter());
+ }
+
+ return generationInfos;
+ }
+ return Collections.emptyList();
+ }
+
+ protected void cleanup() {
+ }
+
+ private void doGenerate(Project project, Editor editor, PsiClass aClass, final ClassMember[] members) {
+ int offset = editor.getCaretModel().getOffset();
+ int col = editor.getCaretModel().getLogicalPosition().column;
+ int line = editor.getCaretModel().getLogicalPosition().line;
+ Document document = editor.getDocument();
+ int lineStartOffset = document.getLineStartOffset(line);
+ CharSequence docText = document.getCharsSequence();
+ String textBeforeCaret = docText.subSequence(lineStartOffset, offset).toString();
+ String afterCaret = docText.subSequence(offset, document.getLineEndOffset(line)).toString();
+ PsiElement lBrace = aClass.getLBrace();
+ if (textBeforeCaret.trim().length() > 0 && StringUtil.isEmptyOrSpaces(afterCaret) && (lBrace == null || lBrace.getTextOffset() < offset) && !editor.getSelectionModel().hasSelection()) {
+ PsiDocumentManager.getInstance(project).commitDocument(document);
+ offset = editor.getCaretModel().getOffset();
+ col = editor.getCaretModel().getLogicalPosition().column;
+ line = editor.getCaretModel().getLogicalPosition().line;
+ }
+
+ int finalOffset = offset;
+ editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(0, 0));
+ List extends GenerationInfo> newMembers = (List) WriteAction.compute(() -> {
+ return GenerateMembersUtil.insertMembersAtOffset(aClass, finalOffset, this.generateMemberPrototypes(aClass, members));
+ });
+ editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(line, col));
+ if (newMembers.isEmpty()) {
+ if (!ApplicationManager.getApplication().isUnitTestMode()) {
+ HintManager.getInstance().showErrorHint(editor, this.getNothingFoundMessage());
+ }
+
+ } else {
+ List templates = new ArrayList();
+ for (GenerationInfo member : newMembers) {
+ if (!(member instanceof TemplateGenerationInfo)) {
+ ContainerUtil.addIfNotNull(templates, member.getPsiMember());
+ }
+ }
+
+ GlobalInspectionContextBase.cleanupElements(project, (Runnable)null, (PsiElement[])templates.toArray(PsiElement.EMPTY_ARRAY));
+ if (!newMembers.isEmpty()) {
+ this.notifyOnSuccess(editor, members, newMembers);
+ }
+
+ }
+ }
+
+ protected void notifyOnSuccess(Editor editor, ClassMember[] members, List extends GenerationInfo> generatedMembers) {
+ ((GenerationInfo)generatedMembers.get(0)).positionCaret(editor, false);
+ }
+
+ protected String getNothingFoundMessage() {
+ return "Nothing found to insert";
+ }
+
+ private static class EntityAttribute implements EncapsulatableClassMember {
+
+ private final PsiClass entityViewClass;
+ private final PsiClass entityClass;
+ private final String attributeName;
+ private final PsiType attributeType;
+ private final Icon myIcon;
+
+ public EntityAttribute(PsiClass entityViewClass, PsiClass entityClass, String attributeName, PsiType attributeType) {
+ this.entityViewClass = entityViewClass;
+ this.entityClass = entityClass;
+ this.attributeName = attributeName;
+ this.attributeType = attributeType;
+ this.myIcon = AllIcons.Javaee.PersistenceAttribute;
+ }
+
+ @Nullable
+ @Override
+ public GenerationInfo generateGetter() throws IncorrectOperationException {
+ StringBuilder definition = new StringBuilder();
+ definition.append("public abstract ").append(attributeType.getCanonicalText());
+ if ("boolean".equals(attributeType.getCanonicalText())) {
+ definition.append(" is");
+ } else {
+ definition.append(" get");
+ }
+ definition.append(Character.toUpperCase(attributeName.charAt(0)));
+ definition.append(attributeName, 1, attributeName.length());
+ definition.append("();");
+ PsiMethod prototype = JavaPsiFacade.getElementFactory(entityViewClass.getProject()).createMethodFromText(definition.toString(), entityViewClass);
+ PsiMethod method = createMethodIfNotExists(entityViewClass, prototype);
+ if (method != null) {
+ return new PsiGenerationInfo(method);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public GenerationInfo generateSetter() throws IncorrectOperationException {
+ StringBuilder definition = new StringBuilder();
+ definition.append("public abstract void set");
+ definition.append(Character.toUpperCase(attributeName.charAt(0)));
+ definition.append(attributeName, 1, attributeName.length());
+ definition.append('(').append(attributeType.getCanonicalText()).append(' ').append(attributeName).append(");");
+ PsiMethod prototype = JavaPsiFacade.getElementFactory(entityViewClass.getProject()).createMethodFromText(definition.toString(), entityViewClass);
+ PsiMethod method = createMethodIfNotExists(entityViewClass, prototype);
+ if (method != null) {
+ return new PsiGenerationInfo(method);
+ }
+
+ return null;
+ }
+
+ @Override
+ public MemberChooserObject getParentNodeDelegate() {
+ String text = PsiFormatUtil.formatClass(entityClass, 2049);
+ return new PsiDocCommentOwnerMemberChooserObject(entityClass, text, entityClass.getIcon(0));
+ }
+
+ @Override
+ public void renderTreeNode(SimpleColoredComponent component, JTree tree) {
+ SpeedSearchUtil.appendFragmentsForSpeedSearch(tree, this.getText(), this.getTextAttributes(tree), false, component);
+ component.setIcon(this.myIcon);
+ }
+
+ @Override
+ public String getText() {
+ return attributeName;
+ }
+
+ protected SimpleTextAttributes getTextAttributes(JTree tree) {
+ return new SimpleTextAttributes(0, tree.getForeground());
+ }
+ }
+
+ @Nullable
+ private static PsiMethod createMethodIfNotExists(PsiClass aClass, PsiMethod template) {
+ PsiMethod existing = aClass.findMethodBySignature(template, false);
+ return existing != null && existing.isPhysical() ? null : template;
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionASTFactory.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionASTFactory.java
new file mode 100644
index 0000000000..614eb6060f
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionASTFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.blazebit.persistence.parser.JPQLNextLexer;
+import com.blazebit.persistence.plugin.intellij.psi.IdentifierPSINode;
+import com.intellij.core.CoreASTFactory;
+import com.intellij.psi.impl.source.tree.CompositeElement;
+import com.intellij.psi.impl.source.tree.FileElement;
+import com.intellij.psi.impl.source.tree.LeafElement;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.tree.IFileElementType;
+
+public class JpqlNextExpressionASTFactory extends CoreASTFactory {
+
+ /** Create a FileElement for root or a parse tree CompositeElement (not
+ * PSI) for the token. This impl is more or less the default.
+ */
+ @Override
+ public CompositeElement createComposite(IElementType type) {
+ if (type instanceof IFileElementType) {
+ return new FileElement(type, null);
+ }
+ return new CompositeElement(type);
+ }
+
+ /** Create PSI nodes out of tokens so even parse tree sees them as such.
+ * Does not see whitespace tokens.
+ */
+ @Override
+ public LeafElement createLeaf(IElementType type, CharSequence text) {
+ LeafElement t;
+ if ( type == JpqlNextExpressionTokenTypes.TOKEN_ELEMENT_TYPES.get(JPQLNextLexer.IDENTIFIER) ) {
+ t = new IdentifierPSINode(type, text);
+ }
+ else {
+ t = super.createLeaf(type, text);
+ }
+ // System.out.println("createLeaf "+t+" from "+type+" "+text);
+ return t;
+ }
+
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionFileType.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionFileType.java
new file mode 100644
index 0000000000..f7de5c58fa
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionFileType.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.intellij.mock.MockLanguageFileType;
+
+public class JpqlNextExpressionFileType extends MockLanguageFileType {
+
+ public static final JpqlNextExpressionFileType INSTANCE = new JpqlNextExpressionFileType();
+
+ public JpqlNextExpressionFileType() {
+ super(JpqlNextExpressionLanguage.INSTANCE, "");
+ }
+
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionLanguage.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionLanguage.java
new file mode 100644
index 0000000000..c8f3fa8bd7
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionLanguage.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.intellij.lang.InjectableLanguage;
+import com.intellij.lang.Language;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import org.jetbrains.annotations.Nullable;
+
+public class JpqlNextExpressionLanguage extends Language implements InjectableLanguage {
+
+ public static final JpqlNextExpressionLanguage INSTANCE = new JpqlNextExpressionLanguage();
+
+ public JpqlNextExpressionLanguage() {
+ super("JPQL.Next-Expression");
+ }
+
+ @Nullable
+ @Override
+ public LanguageFileType getAssociatedFileType() {
+ return JpqlNextExpressionFileType.INSTANCE;
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionParserDefinition.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionParserDefinition.java
new file mode 100644
index 0000000000..f9844d8722
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionParserDefinition.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.blazebit.persistence.parser.JPQLNextLexer;
+import com.blazebit.persistence.parser.JPQLNextParser;
+import com.blazebit.persistence.plugin.intellij.psi.JpqlNextExpressionPSIFileRoot;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.ParserDefinition;
+import com.intellij.lang.PsiParser;
+import com.intellij.lexer.Lexer;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.FileViewProvider;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.tree.IFileElementType;
+import com.intellij.psi.tree.TokenSet;
+import org.antlr.intellij.adaptor.lexer.ANTLRLexerAdaptor;
+import org.antlr.intellij.adaptor.lexer.PSIElementTypeFactory;
+import org.antlr.intellij.adaptor.lexer.RuleIElementType;
+import org.antlr.intellij.adaptor.lexer.TokenIElementType;
+import org.antlr.intellij.adaptor.parser.ANTLRParserAdaptor;
+import org.antlr.intellij.adaptor.psi.ANTLRPsiNode;
+import com.blazebit.persistence.parser.antlr.Parser;
+import com.blazebit.persistence.parser.antlr.tree.ParseTree;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class JpqlNextExpressionParserDefinition implements ParserDefinition {
+ public static final IFileElementType FILE =
+ new IFileElementType(JpqlNextExpressionLanguage.INSTANCE);
+
+ public static TokenIElementType ID;
+
+ static {
+ PSIElementTypeFactory.defineLanguageIElementTypes(JpqlNextExpressionLanguage.INSTANCE,
+ JPQLNextParser.tokenNames,
+ JPQLNextParser.ruleNames);
+ List tokenIElementTypes =
+ PSIElementTypeFactory.getTokenIElementTypes(JpqlNextExpressionLanguage.INSTANCE);
+ ID = tokenIElementTypes.get(JPQLNextLexer.IDENTIFIER);
+ }
+
+ public static final TokenSet COMMENTS =
+ PSIElementTypeFactory.createTokenSet(
+ JpqlNextExpressionLanguage.INSTANCE
+// ,
+// JPQLNextLexer.COMMENT,
+// JPQLNextLexer.LINE_COMMENT
+ );
+
+ public static final TokenSet WHITESPACE =
+ PSIElementTypeFactory.createTokenSet(
+ JpqlNextExpressionLanguage.INSTANCE,
+ JPQLNextLexer.WS);
+
+ public static final TokenSet STRING =
+ PSIElementTypeFactory.createTokenSet(
+ JpqlNextExpressionLanguage.INSTANCE,
+ JPQLNextLexer.STRING_LITERAL);
+
+ @NotNull
+ @Override
+ public Lexer createLexer(Project project) {
+ JPQLNextLexer lexer = new JPQLNextLexer(null);
+ return new ANTLRLexerAdaptor(JpqlNextExpressionLanguage.INSTANCE, lexer);
+ }
+
+ @NotNull
+ public PsiParser createParser(final Project project) {
+ final JPQLNextParser parser = new JPQLNextParser(null);
+ return new ANTLRParserAdaptor(JpqlNextExpressionLanguage.INSTANCE, parser) {
+ @Override
+ protected ParseTree parse(Parser parser, IElementType root) {
+ // start rule depends on root passed in; sometimes we want to create an ID node etc...
+// if ( root instanceof IFileElementType ) {
+// return ((JPQLNextParser) parser).script();
+// }
+ // let's hope it's an ID as needed by "rename function"
+ return ((JPQLNextParser) parser).parseExpression();
+ }
+ };
+ }
+
+ /** "Tokens of those types are automatically skipped by PsiBuilder." */
+ @NotNull
+ @Override
+ public TokenSet getWhitespaceTokens() {
+ return WHITESPACE;
+ }
+
+ @NotNull
+ @Override
+ public TokenSet getCommentTokens() {
+ return COMMENTS;
+ }
+
+ @NotNull
+ @Override
+ public TokenSet getStringLiteralElements() {
+ return STRING;
+ }
+
+ @Override
+ public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode right) {
+ return SpaceRequirements.MAY;
+ }
+
+ /** What is the IFileElementType of the root parse tree node? It
+ * is called from {@link #createFile(FileViewProvider)} at least.
+ */
+ @Override
+ public IFileElementType getFileNodeType() {
+ return FILE;
+ }
+
+ /** Create the root of your PSI tree (a PsiFile).
+ *
+ * From IntelliJ IDEA Architectural Overview:
+ * "A PSI (Program Structure Interface) file is the root of a structure
+ * representing the contents of a file as a hierarchy of elements
+ * in a particular programming language."
+ *
+ * PsiFile is to be distinguished from a FileASTNode, which is a parse
+ * tree node that eventually becomes a PsiFile. From PsiFile, we can get
+ * it back via: {@link PsiFile#getNode}.
+ */
+ @Override
+ public PsiFile createFile(FileViewProvider viewProvider) {
+ return new JpqlNextExpressionPSIFileRoot(viewProvider);
+ }
+
+ /** Convert from *NON-LEAF* parse node (AST they call it)
+ * to PSI node. Leaves are created in the AST factory.
+ * Rename re-factoring can cause this to be
+ * called on a TokenIElementType since we want to rename ID nodes.
+ * In that case, this method is called to create the root node
+ * but with ID type. Kind of strange, but we can simply create a
+ * ASTWrapperPsiElement to make everything work correctly.
+ *
+ * RuleIElementType. Ah! It's that ID is the root
+ * IElementType requested to parse, which means that the root
+ * node returned from parsetree to PSI conversion. But, it
+ * must be a CompositeElement! The adaptor calls
+ * rootMarker.done(root) to finish off the PSI conversion.
+ * See {@link ANTLRParserAdaptor#parse(IElementType root, com.intellij.lang.PsiBuilder)}
+ *
+ * If you don't care to distinguish PSI nodes by type, it is
+ * sufficient to create a {@link ANTLRPsiNode} around
+ * the parse tree node
+ */
+ @NotNull
+ public PsiElement createElement(ASTNode node) {
+ IElementType elType = node.getElementType();
+ if ( elType instanceof TokenIElementType) {
+ return new ANTLRPsiNode(node);
+ }
+ if ( !(elType instanceof RuleIElementType) ) {
+ return new ANTLRPsiNode(node);
+ }
+ RuleIElementType ruleElType = (RuleIElementType) elType;
+ switch ( ruleElType.getRuleIndex() ) {
+// case SampleLanguageParser.RULE_function :
+// return new FunctionSubtree(node, elType);
+// case SampleLanguageParser.RULE_vardef :
+// return new VardefSubtree(node, elType);
+// case SampleLanguageParser.RULE_formal_arg :
+// return new ArgdefSubtree(node, elType);
+// case SampleLanguageParser.RULE_block :
+// return new BlockSubtree(node);
+// case SampleLanguageParser.RULE_call_expr :
+// return new CallSubtree(node);
+ default :
+ return new ANTLRPsiNode(node);
+ }
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionSyntaxHighlighter.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionSyntaxHighlighter.java
new file mode 100644
index 0000000000..d1d31f4f5a
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionSyntaxHighlighter.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.blazebit.persistence.parser.JPQLNextLexer;
+import com.blazebit.persistence.parser.JPQLNextParser;
+import com.intellij.lexer.Lexer;
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
+import com.intellij.openapi.editor.colors.TextAttributesKey;
+import com.intellij.openapi.fileTypes.SyntaxHighlighter;
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.tree.IElementType;
+import org.antlr.intellij.adaptor.lexer.ANTLRLexerAdaptor;
+import org.antlr.intellij.adaptor.lexer.PSIElementTypeFactory;
+import org.antlr.intellij.adaptor.lexer.TokenIElementType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import static com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey;
+
+public class JpqlNextExpressionSyntaxHighlighter extends SyntaxHighlighterBase {
+ private static final TextAttributesKey[] EMPTY_KEYS = new TextAttributesKey[0];
+ public static final TextAttributesKey ID =
+ createTextAttributesKey("JPQL_NEXT_EXPRESSION_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER);
+ public static final TextAttributesKey KEYWORD =
+ createTextAttributesKey("JPQL_NEXT_EXPRESSION_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD);
+ public static final TextAttributesKey STRING =
+ createTextAttributesKey("JPQL_NEXT_EXPRESSION_STRING_LITERAL", DefaultLanguageHighlighterColors.STRING);
+ public static final TextAttributesKey NUMBER =
+ createTextAttributesKey("JPQL_NEXT_EXPRESSION_NUMBER", DefaultLanguageHighlighterColors.NUMBER);
+
+ static {
+ PSIElementTypeFactory.defineLanguageIElementTypes(JpqlNextExpressionLanguage.INSTANCE,
+ JPQLNextParser.tokenNames,
+ JPQLNextParser.ruleNames);
+ }
+
+ @NotNull
+ @Override
+ public Lexer getHighlightingLexer() {
+ JPQLNextLexer lexer = new JPQLNextLexer(null);
+ return new ANTLRLexerAdaptor(JpqlNextExpressionLanguage.INSTANCE, lexer);
+ }
+
+ @NotNull
+ @Override
+ public TextAttributesKey[] getTokenHighlights(IElementType tokenType) {
+ if ( !(tokenType instanceof TokenIElementType) ) return EMPTY_KEYS;
+ TokenIElementType myType = (TokenIElementType)tokenType;
+ int ttype = myType.getANTLRTokenType();
+ TextAttributesKey attrKey;
+ switch ( ttype ) {
+ case JPQLNextLexer.IDENTIFIER :
+ attrKey = ID;
+ break;
+
+ case JPQLNextLexer.AFTER:
+ case JPQLNextLexer.ALL:
+ case JPQLNextLexer.AND:
+ case JPQLNextLexer.ANY:
+ case JPQLNextLexer.AS:
+ case JPQLNextLexer.ASC:
+ case JPQLNextLexer.BEFORE:
+ case JPQLNextLexer.BETWEEN:
+ case JPQLNextLexer.BOTH:
+ case JPQLNextLexer.BY:
+ case JPQLNextLexer.CASE:
+ case JPQLNextLexer.COLLATE:
+ case JPQLNextLexer.CONTAINING:
+ case JPQLNextLexer.COUNT:
+ case JPQLNextLexer.CROSS:
+ case JPQLNextLexer.CURRENT:
+ case JPQLNextLexer.CURRENT_DATE:
+ case JPQLNextLexer.CURRENT_INSTANT:
+ case JPQLNextLexer.CURRENT_TIME:
+ case JPQLNextLexer.CURRENT_TIMESTAMP:
+ case JPQLNextLexer.DELETE:
+ case JPQLNextLexer.DESC:
+ case JPQLNextLexer.DISTINCT:
+ case JPQLNextLexer.ELSE:
+ case JPQLNextLexer.EMPTY:
+ case JPQLNextLexer.END:
+ case JPQLNextLexer.ENTRY:
+ case JPQLNextLexer.ESCAPE:
+ case JPQLNextLexer.EXCEPT:
+ case JPQLNextLexer.EXCLUDE:
+ case JPQLNextLexer.EXISTS:
+ case JPQLNextLexer.FALSE:
+ case JPQLNextLexer.FETCH:
+ case JPQLNextLexer.FILTER:
+ case JPQLNextLexer.FIRST:
+ case JPQLNextLexer.FOLLOWING:
+ case JPQLNextLexer.FROM:
+ case JPQLNextLexer.FULL:
+ case JPQLNextLexer.GROUP:
+ case JPQLNextLexer.GROUPS:
+ case JPQLNextLexer.HAVING:
+ case JPQLNextLexer.IN:
+ case JPQLNextLexer.INDEX:
+ case JPQLNextLexer.INNER:
+ case JPQLNextLexer.INSERT:
+ case JPQLNextLexer.INTERSECT:
+ case JPQLNextLexer.INTO:
+ case JPQLNextLexer.IS:
+ case JPQLNextLexer.JOIN:
+ case JPQLNextLexer.JUMP:
+ case JPQLNextLexer.KEY:
+ case JPQLNextLexer.LAST:
+ case JPQLNextLexer.LEADING:
+ case JPQLNextLexer.LEFT:
+ case JPQLNextLexer.LIKE:
+ case JPQLNextLexer.LIMIT:
+ case JPQLNextLexer.MEMBER:
+ case JPQLNextLexer.NEW:
+ case JPQLNextLexer.NO:
+ case JPQLNextLexer.NOT:
+ case JPQLNextLexer.NULL:
+ case JPQLNextLexer.NULLS:
+ case JPQLNextLexer.OBJECT:
+ case JPQLNextLexer.OF:
+ case JPQLNextLexer.OFFSET:
+ case JPQLNextLexer.OLD:
+ case JPQLNextLexer.ON:
+ case JPQLNextLexer.OR:
+ case JPQLNextLexer.ORDER:
+ case JPQLNextLexer.OTHERS:
+ case JPQLNextLexer.OUTER:
+ case JPQLNextLexer.OVER:
+ case JPQLNextLexer.PAGE:
+ case JPQLNextLexer.PARTITION:
+ case JPQLNextLexer.PRECEDING:
+ case JPQLNextLexer.RANGE:
+ case JPQLNextLexer.RECURSIVE:
+ case JPQLNextLexer.RETURNING:
+ case JPQLNextLexer.RIGHT:
+ case JPQLNextLexer.ROW:
+ case JPQLNextLexer.ROWS:
+ case JPQLNextLexer.SELECT:
+ case JPQLNextLexer.SET:
+ case JPQLNextLexer.SOME:
+ case JPQLNextLexer.THEN:
+ case JPQLNextLexer.TIES:
+ case JPQLNextLexer.TO:
+ case JPQLNextLexer.TRAILING:
+ case JPQLNextLexer.TREAT:
+ case JPQLNextLexer.TRIM:
+ case JPQLNextLexer.TRUE:
+ case JPQLNextLexer.TYPE:
+ case JPQLNextLexer.UNBOUNDED:
+ case JPQLNextLexer.UNION:
+ case JPQLNextLexer.UPDATE:
+ case JPQLNextLexer.VALUE:
+ case JPQLNextLexer.VALUES:
+ case JPQLNextLexer.WHEN:
+ case JPQLNextLexer.WHERE:
+ case JPQLNextLexer.WINDOW:
+ case JPQLNextLexer.WITH:
+ attrKey = KEYWORD;
+ break;
+ case JPQLNextLexer.STRING_LITERAL :
+ case JPQLNextLexer.CHARACTER_LITERAL:
+ attrKey = STRING;
+ break;
+ case JPQLNextLexer.BIG_DECIMAL_LITERAL:
+ case JPQLNextLexer.LONG_LITERAL:
+ case JPQLNextLexer.BIG_INTEGER_LITERAL:
+ case JPQLNextLexer.DOUBLE_LITERAL:
+ case JPQLNextLexer.FLOAT_LITERAL:
+ case JPQLNextLexer.INTEGER_LITERAL:
+ attrKey = NUMBER;
+ break;
+ default :
+ return EMPTY_KEYS;
+ }
+ return new TextAttributesKey[] {attrKey};
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionSyntaxHighlighterFactory.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionSyntaxHighlighterFactory.java
new file mode 100644
index 0000000000..8b5751caf0
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionSyntaxHighlighterFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighter;
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class JpqlNextExpressionSyntaxHighlighterFactory extends SyntaxHighlighterFactory {
+
+ @NotNull
+ @Override
+ public SyntaxHighlighter getSyntaxHighlighter(@Nullable Project project, @Nullable VirtualFile virtualFile) {
+ return new JpqlNextExpressionSyntaxHighlighter();
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionTokenTypes.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionTokenTypes.java
new file mode 100644
index 0000000000..1eb007df00
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/JpqlNextExpressionTokenTypes.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.blazebit.persistence.parser.JPQLNextLexer;
+import com.blazebit.persistence.parser.JPQLNextParser;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.tree.TokenSet;
+import org.antlr.intellij.adaptor.lexer.PSIElementTypeFactory;
+import org.antlr.intellij.adaptor.lexer.RuleIElementType;
+import org.antlr.intellij.adaptor.lexer.TokenIElementType;
+import org.intellij.lang.annotations.MagicConstant;
+
+import java.util.List;
+
+public class JpqlNextExpressionTokenTypes {
+
+ public static IElementType BAD_TOKEN_TYPE = new IElementType("BAD_TOKEN", JpqlNextExpressionLanguage.INSTANCE);
+
+ public static final List TOKEN_ELEMENT_TYPES =
+ PSIElementTypeFactory.getTokenIElementTypes(JpqlNextExpressionLanguage.INSTANCE);
+ public static final List RULE_ELEMENT_TYPES =
+ PSIElementTypeFactory.getRuleIElementTypes(JpqlNextExpressionLanguage.INSTANCE);
+
+ public static final TokenSet COMMENTS =
+ PSIElementTypeFactory.createTokenSet(
+ JpqlNextExpressionLanguage.INSTANCE
+ // We don't have any comments
+// ,
+// JPQLNextLexer.DOC_COMMENT,
+// JPQLNextLexer.BLOCK_COMMENT,
+// JPQLNextLexer.LINE_COMMENT
+ );
+
+ public static final TokenSet WHITESPACES =
+ PSIElementTypeFactory.createTokenSet(
+ JpqlNextExpressionLanguage.INSTANCE,
+ JPQLNextLexer.WS);
+
+ public static final TokenSet KEYWORDS =
+ PSIElementTypeFactory.createTokenSet(
+ JpqlNextExpressionLanguage.INSTANCE,
+ JPQLNextLexer.AFTER,
+ JPQLNextLexer.ALL,
+ JPQLNextLexer.AND,
+ JPQLNextLexer.ANY,
+ JPQLNextLexer.AS,
+ JPQLNextLexer.ASC,
+ JPQLNextLexer.BEFORE,
+ JPQLNextLexer.BETWEEN,
+ JPQLNextLexer.BOTH,
+ JPQLNextLexer.BY,
+ JPQLNextLexer.CASE,
+ JPQLNextLexer.COLLATE,
+ JPQLNextLexer.CONTAINING,
+ JPQLNextLexer.COUNT,
+ JPQLNextLexer.CROSS,
+ JPQLNextLexer.CURRENT,
+ JPQLNextLexer.CURRENT_DATE,
+ JPQLNextLexer.CURRENT_INSTANT,
+ JPQLNextLexer.CURRENT_TIME,
+ JPQLNextLexer.CURRENT_TIMESTAMP,
+ JPQLNextLexer.DELETE,
+ JPQLNextLexer.DESC,
+ JPQLNextLexer.DISTINCT,
+ JPQLNextLexer.ELSE,
+ JPQLNextLexer.EMPTY,
+ JPQLNextLexer.END,
+ JPQLNextLexer.ENTRY,
+ JPQLNextLexer.ESCAPE,
+ JPQLNextLexer.EXCEPT,
+ JPQLNextLexer.EXCLUDE,
+ JPQLNextLexer.EXISTS,
+ JPQLNextLexer.FALSE,
+ JPQLNextLexer.FETCH,
+ JPQLNextLexer.FILTER,
+ JPQLNextLexer.FIRST,
+ JPQLNextLexer.FOLLOWING,
+ JPQLNextLexer.FROM,
+ JPQLNextLexer.FULL,
+ JPQLNextLexer.GROUP,
+ JPQLNextLexer.GROUPS,
+ JPQLNextLexer.HAVING,
+ JPQLNextLexer.IN,
+ JPQLNextLexer.INDEX,
+ JPQLNextLexer.INNER,
+ JPQLNextLexer.INSERT,
+ JPQLNextLexer.INTERSECT,
+ JPQLNextLexer.INTO,
+ JPQLNextLexer.IS,
+ JPQLNextLexer.JOIN,
+ JPQLNextLexer.JUMP,
+ JPQLNextLexer.KEY,
+ JPQLNextLexer.LAST,
+ JPQLNextLexer.LEADING,
+ JPQLNextLexer.LEFT,
+ JPQLNextLexer.LIKE,
+ JPQLNextLexer.LIMIT,
+ JPQLNextLexer.MEMBER,
+ JPQLNextLexer.NEW,
+ JPQLNextLexer.NO,
+ JPQLNextLexer.NOT,
+ JPQLNextLexer.NULL,
+ JPQLNextLexer.NULLS,
+ JPQLNextLexer.OBJECT,
+ JPQLNextLexer.OF,
+ JPQLNextLexer.OFFSET,
+ JPQLNextLexer.OLD,
+ JPQLNextLexer.ON,
+ JPQLNextLexer.OR,
+ JPQLNextLexer.ORDER,
+ JPQLNextLexer.OTHERS,
+ JPQLNextLexer.OUTER,
+ JPQLNextLexer.OVER,
+ JPQLNextLexer.PAGE,
+ JPQLNextLexer.PARTITION,
+ JPQLNextLexer.PRECEDING,
+ JPQLNextLexer.RANGE,
+ JPQLNextLexer.RECURSIVE,
+ JPQLNextLexer.RETURNING,
+ JPQLNextLexer.RIGHT,
+ JPQLNextLexer.ROW,
+ JPQLNextLexer.ROWS,
+ JPQLNextLexer.SELECT,
+ JPQLNextLexer.SET,
+ JPQLNextLexer.SOME,
+ JPQLNextLexer.THEN,
+ JPQLNextLexer.TIES,
+ JPQLNextLexer.TO,
+ JPQLNextLexer.TRAILING,
+ JPQLNextLexer.TREAT,
+ JPQLNextLexer.TRIM,
+ JPQLNextLexer.TRUE,
+ JPQLNextLexer.TYPE,
+ JPQLNextLexer.UNBOUNDED,
+ JPQLNextLexer.UNION,
+ JPQLNextLexer.UPDATE,
+ JPQLNextLexer.VALUE,
+ JPQLNextLexer.VALUES,
+ JPQLNextLexer.WHEN,
+ JPQLNextLexer.WHERE,
+ JPQLNextLexer.WINDOW,
+ JPQLNextLexer.WITH
+ );
+
+ public static RuleIElementType getRuleElementType(@MagicConstant(valuesFromClass = JPQLNextParser.class)int ruleIndex){
+ return RULE_ELEMENT_TYPES.get(ruleIndex);
+ }
+ public static TokenIElementType getTokenElementType(@MagicConstant(valuesFromClass = JPQLNextLexer.class)int ruleIndex){
+ return TOKEN_ELEMENT_TYPES.get(ruleIndex);
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/NewEntityViewAction.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/NewEntityViewAction.java
new file mode 100644
index 0000000000..267a3421f4
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/NewEntityViewAction.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+
+import java.util.Arrays;
+
+public class NewEntityViewAction extends AnAction {
+
+ @Override
+ public void actionPerformed(AnActionEvent event) {
+ Project project = event.getData(PlatformDataKeys.PROJECT);
+ PsiElement element = event.getData(PlatformDataKeys.PSI_ELEMENT);
+ if (element instanceof PsiClass) {
+ PsiFile file = event.getData(PlatformDataKeys.PSI_FILE);
+ if (Arrays.asList(file.getChildren()).contains(element)) {
+ new EditEntityViewDialog(project, (PsiClass) element).show();
+ }
+ }
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/psi/IdentifierPSINode.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/psi/IdentifierPSINode.java
new file mode 100644
index 0000000000..feee62dd66
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/psi/IdentifierPSINode.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij.psi;
+
+import com.blazebit.persistence.plugin.intellij.JpqlNextExpressionLanguage;
+import com.blazebit.persistence.plugin.intellij.JpqlNextExpressionParserDefinition;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiNamedElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.util.IncorrectOperationException;
+import org.antlr.intellij.adaptor.lexer.RuleIElementType;
+import org.antlr.intellij.adaptor.psi.ANTLRPsiLeafNode;
+import org.antlr.intellij.adaptor.psi.Trees;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+public class IdentifierPSINode extends ANTLRPsiLeafNode implements PsiNamedElement {
+
+ public IdentifierPSINode(IElementType type, CharSequence text) {
+ super(type, text);
+ }
+
+ @Override
+ public String getName() {
+ return getText();
+ }
+
+ /** Alter this node to have text specified by the argument. Do this by
+ * creating a new node through parsing of an ID and then doing a
+ * replace.
+ */
+ @Override
+ public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
+ if ( getParent()==null ) return this; // weird but it happened once
+ /*
+ IElementType elType = getParent().getNode().getElementType();
+ String kind = "??? ";
+ if ( elType instanceof RuleIElementType ) {
+ int ruleIndex = ((RuleIElementType) elType).getRuleIndex();
+ if ( ruleIndex == RULE_call_expr ) {
+ kind = "call ";
+ }
+ else if ( ruleIndex == RULE_statement ) {
+ kind = "assign ";
+ }
+ else if ( ruleIndex == RULE_function ) {
+ kind = "func def ";
+ }
+ }
+ System.out.println("IdentifierPSINode.setName("+name+") on "+
+ kind+this+" at "+Integer.toHexString(this.hashCode()));
+ */
+ PsiElement newID = Trees.createLeafFromText(getProject(),
+ JpqlNextExpressionLanguage.INSTANCE,
+ getContext(),
+ name,
+ JpqlNextExpressionParserDefinition.ID);
+ if ( newID!=null ) {
+ return this.replace(newID); // use replace on leaves but replaceChild on ID nodes that are part of defs/decls.
+ }
+ return this;
+ }
+
+ /** Create and return a PsiReference object associated with this ID
+ * node. The reference object will be asked to resolve this ref
+ * by using the text of this node to identify the appropriate definition
+ * site. The definition site is typically a subtree for a function
+ * or variable definition whereas this reference is just to this ID
+ * leaf node.
+ *
+ * As the AST factory has no context and cannot create different kinds
+ * of PsiNamedElement nodes according to context, every ID node
+ * in the tree will be of this type. So, we distinguish references
+ * from definitions or other uses by looking at context in this method
+ * as we have parent (context) information.
+ */
+ @Override
+ public PsiReference getReference() {
+ PsiElement parent = getParent();
+ IElementType elType = parent.getNode().getElementType();
+ // do not return a reference for the ID nodes in a definition
+// if ( elType instanceof RuleIElementType) {
+// switch ( ((RuleIElementType) elType).getRuleIndex() ) {
+// case RULE_statement :
+// case RULE_expr :
+// case RULE_primary :
+// return new VariableRef(this);
+// case RULE_call_expr :
+// return new FunctionRef(this);
+// }
+// }
+ return null;
+ }
+}
diff --git a/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/psi/JpqlNextExpressionPSIFileRoot.java b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/psi/JpqlNextExpressionPSIFileRoot.java
new file mode 100644
index 0000000000..154f016b47
--- /dev/null
+++ b/plugin/intellij/src/main/java/com/blazebit/persistence/plugin/intellij/psi/JpqlNextExpressionPSIFileRoot.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2014 - 2019 Blazebit.
+ *
+ * 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 com.blazebit.persistence.plugin.intellij.psi;
+
+import com.blazebit.persistence.plugin.intellij.JpqlNextExpressionFileType;
+import com.blazebit.persistence.plugin.intellij.JpqlNextExpressionLanguage;
+import com.intellij.extapi.psi.PsiFileBase;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.psi.FileViewProvider;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiNamedElement;
+import org.antlr.intellij.adaptor.SymtabUtils;
+import org.antlr.intellij.adaptor.psi.ScopeNode;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class JpqlNextExpressionPSIFileRoot extends PsiFileBase implements ScopeNode {
+
+ public JpqlNextExpressionPSIFileRoot(@NotNull FileViewProvider viewProvider) {
+ super(viewProvider, JpqlNextExpressionLanguage.INSTANCE);
+ }
+
+ @NotNull
+ @Override
+ public FileType getFileType() {
+ return JpqlNextExpressionFileType.INSTANCE;
+ }
+
+ @Override
+ public String toString() {
+ return "Sample Language file";
+ }
+
+ @Override
+ public Icon getIcon(int flags) {
+ return AllIcons.Javaee.PersistenceEntity;
+ }
+
+ /** Return null since a file scope has no enclosing scope. It is
+ * not itself in a scope.
+ */
+ @Override
+ public ScopeNode getContext() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public PsiElement resolve(PsiNamedElement element) {
+ // System.out.println(getClass().getSimpleName()+
+ // ".resolve("+element.getName()+
+ // " at "+Integer.toHexString(element.hashCode())+")");
+// if ( element.getParent() instanceof CallSubtree ) {
+// return SymtabUtils.resolve(this, SampleLanguage.INSTANCE,
+// element, "/script/function/ID");
+// }
+// return SymtabUtils.resolve(this, SampleLanguage.INSTANCE,
+// element, "/script/vardef/ID");
+ return null;
+ }
+}
diff --git a/plugin/intellij/src/main/resources/META-INF/plugin.xml b/plugin/intellij/src/main/resources/META-INF/plugin.xml
new file mode 100644
index 0000000000..5715d963a8
--- /dev/null
+++ b/plugin/intellij/src/main/resources/META-INF/plugin.xml
@@ -0,0 +1,57 @@
+
+
+
+ com.blazebit.persistence.intellij
+ Blaze-Persistence Support
+ 1.0
+ Blaze-Persistence
+
+
+
+
+
+ com.intellij.modules.java
+ org.intellij.intelliLang
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin/intellij/src/main/resources/javaInjections.xml b/plugin/intellij/src/main/resources/javaInjections.xml
new file mode 100644
index 0000000000..cc9efd1e12
--- /dev/null
+++ b/plugin/intellij/src/main/resources/javaInjections.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ Entity View Mapping
+
+
+
\ No newline at end of file
diff --git a/plugin/pom.xml b/plugin/pom.xml
new file mode 100644
index 0000000000..ddfa99f4f6
--- /dev/null
+++ b/plugin/pom.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ 4.0.0
+
+
+ com.blazebit
+ blaze-persistence-parent
+ 1.4.0-SNAPSHOT
+ ../parent/pom.xml
+
+
+ blaze-persistence-plugin
+ pom
+
+ Blazebit Persistence Plugins
+
+ antlr
+ intellij
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 5414da552c..16e3134dbc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,6 +58,7 @@
checkstyle-rules
archetype
dist
+ plugin