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 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 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