diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..2e7379ea --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: "CodeQL" + +on: + push: + branches: [ "master", "develop" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: "28 7 * * 6" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ java ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 0d8244ed..2f10fa61 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -10,15 +10,20 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: 1.8 - java-package: jdk + distribution: temurin + java-version: 8 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 - name: Gradle Plugin Publish Local run: | ./gradlew :Packager:publishToMavenLocal + env: + PGP_KEY: ${{ secrets.PGP_KEY }} + PGP_PWD: ${{ secrets.PGP_PWD }} build: @@ -26,14 +31,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: 1.8 - java-package: jdk - - name: Grant execute permission for gradlew - run: chmod +x gradlew + distribution: temurin + java-version: 8 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 - name: Build with Gradle run: ./gradlew build @@ -45,9 +50,12 @@ jobs: steps: - uses: actions/checkout@v1 - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: 1.8 + distribution: temurin + java-version: 8 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 - name: Gradle Maven Publish run: | ./gradlew publishMavenJavaPublicationToMavenRepository diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 6aff882c..45150ab0 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -10,14 +10,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: 1.8 - java-package: jdk - - name: Grant execute permission for gradlew - run: chmod +x gradlew + distribution: temurin + java-version: 8 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 - name: Build with Gradle run: ./gradlew build - name: JaCoco Test Report @@ -33,11 +33,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: 1.8 + distribution: temurin + java-version: 8 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 - name: Gradle Maven Publish run: | ./gradlew publishMavenJavaPublicationToMavenRepository diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3e2e265b..e73eef42 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -11,19 +11,19 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: 1.8 - java-package: jdk - - name: Grant execute permission for gradlew - run: chmod +x gradlew + distribution: temurin + java-version: 8 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 - name: Run unit tests run: ./gradlew Library:test - name: Upload Failing Unit Test Results if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: Unit and Int Test Failure Results path: ./Library/build/reports/tests/** diff --git a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Action.java b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Action.java index ce829ac2..31c279bc 100644 --- a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Action.java +++ b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Action.java @@ -28,17 +28,30 @@ /** * Action Annotation *

- * Target is a Method - *

- *

- * If your method only has @Data annotated parameters, it will be called automatically by the SDK. - * If it is not the case, the SDK will call the TouchPortalPluginListener.onReceive(JsonObject jsonMessage) instead. + * Target is a Method or a Class extending TPAction *

+ * * * @see TP Documentation: Dynamic Actions */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.TYPE}) public @interface Action { /** * Action id diff --git a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ActionTranslation.java b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ActionTranslation.java new file mode 100644 index 00000000..6338933b --- /dev/null +++ b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ActionTranslation.java @@ -0,0 +1,80 @@ +/* + * Touch Portal Plugin SDK + * + * Copyright 2020 Christophe Carvalho Vilas-Boas + * christophe.carvalhovilasboas@gmail.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.christophecvb.touchportal.annotations; + +import java.lang.annotation.*; + +/** + * ActionTranslation Annotation + *

+ * Target is a Method + *

+ */ +@Repeatable(ActionTranslations.class) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface ActionTranslation { + /** + * ActionTranslation Language + * + * @return Language language + */ + Language language(); + + /** + * ActionTranslation name + *

+ * Default is "" + *

+ * + * @return String name + */ + String name() default ""; + + /** + * ActionTranslation prefix + *

+ * Default is "" + *

+ * + * @return String prefix + */ + String prefix() default ""; + + /** + * ActionTranslation description + *

+ * Default is "" + *

+ * + * @return String description + */ + String description() default ""; + + /** + * ActionTranslation format + *

+ * Default is "" + *

+ * + * @return String format + */ + String format() default ""; +} diff --git a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ActionTranslations.java b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ActionTranslations.java new file mode 100644 index 00000000..37bcd533 --- /dev/null +++ b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ActionTranslations.java @@ -0,0 +1,9 @@ +package com.christophecvb.touchportal.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface ActionTranslations { + ActionTranslation[] value(); +} diff --git a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Connector.java b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Connector.java index d07ecfc2..5aafdd04 100644 --- a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Connector.java +++ b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Connector.java @@ -28,17 +28,30 @@ /** * Connector Annotation *

- * Target is a Method - *

- *

- * If your method only has @Data annotated parameters, it will be called automatically by the SDK. - * If it is not the case, the SDK will call the TouchPortalPluginListener.onReceive(JsonObject jsonMessage) instead. + * Target is a Method or a Class extending TPConnector *

+ * * * @see TP Documentation: Connectors */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.TYPE}) public @interface Connector { /** * Connector id diff --git a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ConnectorValue.java b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ConnectorValue.java index de3eaa13..c1fdc122 100644 --- a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ConnectorValue.java +++ b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ConnectorValue.java @@ -38,6 +38,6 @@ * @see TP Documentation: Connectors */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.PARAMETER) +@Target({ElementType.PARAMETER, ElementType.FIELD}) public @interface ConnectorValue { } diff --git a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Data.java b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Data.java index 1a57faf5..a23a9429 100644 --- a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Data.java +++ b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Data.java @@ -34,7 +34,7 @@ * @see TP Documentation: Action Data */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.PARAMETER) +@Target({ElementType.PARAMETER, ElementType.FIELD}) public @interface Data { /** * Data id diff --git a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Language.java b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Language.java new file mode 100644 index 00000000..880e2dbf --- /dev/null +++ b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Language.java @@ -0,0 +1,25 @@ +package com.christophecvb.touchportal.annotations; + +/** + * Languages supported by Touch Portal + */ +public enum Language { + //ENGLISH("en"), This is the default language to provide in Action annotation + GERMAN("de"), + SPANISH("es"), + FRENCH("fr"), + DUTCH("nl"), + PORTUGUESE("pt"), + TURKISH("tr"); + + + private final String code; + + Language(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } +} diff --git a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ParentCategory.java b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ParentCategory.java new file mode 100644 index 00000000..d7611fcc --- /dev/null +++ b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/ParentCategory.java @@ -0,0 +1,25 @@ +package com.christophecvb.touchportal.annotations; + +/** + * Parent Categories supported by Touch Portal + */ +public enum ParentCategory { + AUDIO("audio"), + STREAMING("streaming"), + CONTENT("content"), + HOME_AUTOMATION("homeautomation"), + SOCIAL("social "), + GAMES("games"), + MISC("misc"); + + + private final String key; + + ParentCategory(String key) { + this.key = key; + } + + public String getKey() { + return this.key; + } +} diff --git a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Plugin.java b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Plugin.java index 418b32a9..8f583b96 100644 --- a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Plugin.java +++ b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Plugin.java @@ -72,4 +72,14 @@ * @return String colorLight */ String colorLight(); + + /** + * Plugin Parent Category + *

+ * Value from enum {@link ParentCategory} + *

+ * + * @return ParentCategory parentCategory + */ + ParentCategory parentCategory() default ParentCategory.MISC; } diff --git a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Setting.java b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Setting.java index eded71fb..6d276dca 100644 --- a/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Setting.java +++ b/Annotations/src/main/java/com/christophecvb/touchportal/annotations/Setting.java @@ -99,4 +99,17 @@ * @return boolean isReadOnly */ boolean isReadOnly() default false; + + /** + * Setting Tooltip + * + * @return {@link Tooltip} tooltip + */ + Tooltip tooltip() default @Tooltip(body = ""); + + @interface Tooltip { + String title() default ""; + String body(); + String docUrl() default ""; + } } diff --git a/AnnotationsProcessor/build.gradle b/AnnotationsProcessor/build.gradle index 67a555eb..96232ca1 100644 --- a/AnnotationsProcessor/build.gradle +++ b/AnnotationsProcessor/build.gradle @@ -77,11 +77,11 @@ javadoc { } dependencies { - implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0' - implementation 'com.google.auto.service:auto-service:1.0.1' - implementation group: 'com.squareup', name: 'javapoet', version: '1.13.0' + implementation libs.gson + implementation libs.autoservice + implementation libs.javapoet implementation project(':Annotations') implementation project(':Helpers') - testImplementation group: 'junit', name: 'junit', version: '4.13.2' + testImplementation libs.junit } diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/ActionProcessor.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/ActionProcessor.java new file mode 100644 index 00000000..5eb59706 --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/ActionProcessor.java @@ -0,0 +1,88 @@ +package com.christophecvb.touchportal.annotations.processor; + +import com.christophecvb.touchportal.annotations.*; +import com.christophecvb.touchportal.annotations.processor.utils.Pair; +import com.christophecvb.touchportal.annotations.processor.utils.SpecUtils; +import com.christophecvb.touchportal.helpers.ActionHelper; +import com.christophecvb.touchportal.helpers.GenericHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.squareup.javapoet.TypeSpec; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.tools.Diagnostic; +import java.util.Set; + +public class ActionProcessor { + /** + * Generates a JsonObject and a TypeSpec.Builder representing the {@link Action} + * + * @param processor {@link TouchPortalPluginAnnotationsProcessor} + * @param roundEnv RoundEnvironment + * @param pluginElement Element + * @param plugin {@link Plugin} + * @param categoryElement Element + * @param category {@link Category} + * @param actionElement Element + * @return Pair<JsonObject, TypeSpec.Builder> actionPair + * @throws GenericHelper.TPTypeException If a used type is not Supported + */ + public static Pair process(TouchPortalPluginAnnotationsProcessor processor, RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element actionElement) throws GenericHelper.TPTypeException { + processor.getMessager().printMessage(Diagnostic.Kind.NOTE, "Process Action: " + actionElement.getSimpleName()); + Action action = actionElement.getAnnotation(Action.class); + + TypeSpec.Builder actionTypeSpecBuilder = SpecUtils.createActionTypeSpecBuilder(pluginElement, categoryElement, category, actionElement, action); + + JsonObject jsonAction = new JsonObject(); + jsonAction.addProperty(ActionHelper.ID, ActionHelper.getActionId(pluginElement, categoryElement, category, actionElement, action)); + jsonAction.addProperty(ActionHelper.NAME, ActionHelper.getActionName(actionElement, action)); + jsonAction.addProperty(ActionHelper.PREFIX, action.prefix()); + jsonAction.addProperty(ActionHelper.TYPE, action.type()); + if (!action.description().isEmpty()) { + jsonAction.addProperty(ActionHelper.DESCRIPTION, action.description()); + } + if (!action.format().isEmpty()) { + jsonAction.addProperty(ActionHelper.FORMAT, action.format()); + jsonAction.addProperty(ActionHelper.TRY_INLINE, true); + } + jsonAction.addProperty(ActionHelper.HAS_HOLD_FUNCTIONALITY, action.hasHoldFunctionality()); + + ActionTranslation[] actionTranslations = actionElement.getAnnotationsByType(ActionTranslation.class); + if (actionTranslations.length > 0) { + for (ActionTranslation actionTranslation : actionTranslations) { + String languageCode = actionTranslation.language().getCode(); + if (!actionTranslation.name().isEmpty()) { + jsonAction.addProperty(ActionHelper.NAME + "_" + languageCode, actionTranslation.name()); + } + if (!actionTranslation.prefix().isEmpty()) { + jsonAction.addProperty(ActionHelper.PREFIX + "_" + languageCode, actionTranslation.prefix()); + } + if (!actionTranslation.description().isEmpty()) { + jsonAction.addProperty(ActionHelper.DESCRIPTION + "_" + languageCode, actionTranslation.description()); + } + if (!actionTranslation.format().isEmpty()) { + jsonAction.addProperty(ActionHelper.FORMAT + "_" + languageCode, actionTranslation.format()); + } + } + } + + JsonArray jsonActionData = new JsonArray(); + Set dataElements = roundEnv.getElementsAnnotatedWith(Data.class); + for (Element dataElement : dataElements) { + Element enclosingElement = dataElement.getEnclosingElement(); + if (actionElement.equals(enclosingElement)) { + Pair actionDataResult = DataProcessor.process(processor, roundEnv, pluginElement, plugin, categoryElement, category, actionElement, action, jsonAction, dataElement); + jsonActionData.add(actionDataResult.first); + if (actionDataResult.second != null) { + actionTypeSpecBuilder.addType(actionDataResult.second.build()); + } + } + } + if (jsonActionData.size() > 0) { + jsonAction.add(ActionHelper.DATA, jsonActionData); + } + + return Pair.create(jsonAction, actionTypeSpecBuilder); + } +} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/CategoryProcessor.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/CategoryProcessor.java new file mode 100644 index 00000000..acd990f1 --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/CategoryProcessor.java @@ -0,0 +1,110 @@ +package com.christophecvb.touchportal.annotations.processor; + +import com.christophecvb.touchportal.annotations.*; +import com.christophecvb.touchportal.annotations.processor.utils.Pair; +import com.christophecvb.touchportal.annotations.processor.utils.SpecUtils; +import com.christophecvb.touchportal.helpers.CategoryHelper; +import com.christophecvb.touchportal.helpers.GenericHelper; +import com.christophecvb.touchportal.helpers.PluginHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.squareup.javapoet.TypeSpec; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.tools.Diagnostic; +import java.util.Set; + +public class CategoryProcessor { + /** + * Generates a JsonObject and a TypeSpec.Builder representing the {@link Category} + * + * @param processor {@link TouchPortalPluginAnnotationsProcessor} + * @param roundEnv RoundEnvironment + * @param pluginElement Element + * @param plugin {@link Plugin} + * @param categoryElement Element + * @return Pair<JsonObject, TypeSpec.Builder> categoryPair + * @throws GenericHelper.TPTypeException If a used type is not Supported + * @throws TPAnnotationException If an Annotation is misused + */ + public static Pair process(TouchPortalPluginAnnotationsProcessor processor, RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement) throws GenericHelper.TPTypeException, TPAnnotationException { + processor.getMessager().printMessage(Diagnostic.Kind.NOTE, "Process Category: " + categoryElement.getSimpleName()); + Category category = categoryElement.getAnnotation(Category.class); + + TypeSpec.Builder categoryTypeSpecBuilder = SpecUtils.createCategoryTypeSpecBuilder(pluginElement, categoryElement, category); + + JsonObject jsonCategory = new JsonObject(); + jsonCategory.addProperty(CategoryHelper.ID, CategoryHelper.getCategoryId(pluginElement, categoryElement, category)); + jsonCategory.addProperty(CategoryHelper.NAME, CategoryHelper.getCategoryName(categoryElement, category)); + jsonCategory.addProperty(CategoryHelper.IMAGE_PATH, PluginHelper.TP_PLUGIN_FOLDER + pluginElement.getSimpleName() + "/" + category.imagePath()); + + TypeSpec.Builder actionsTypeSpecBuilder = TypeSpec.classBuilder("Actions").addModifiers(Modifier.PUBLIC, Modifier.STATIC); + JsonArray jsonActions = new JsonArray(); + Set actionElements = roundEnv.getElementsAnnotatedWith(Action.class); + for (Element actionElement : actionElements) { + Action action = actionElement.getAnnotation(Action.class); + String categoryId = category.id().isEmpty() ? categoryElement.getSimpleName().toString() : category.id(); + if (categoryId.equals(action.categoryId())) { + Pair actionResult = ActionProcessor.process(processor, roundEnv, pluginElement, plugin, categoryElement, category, actionElement); + jsonActions.add(actionResult.first); + actionsTypeSpecBuilder.addType(actionResult.second.build()); + } + } + categoryTypeSpecBuilder.addType(actionsTypeSpecBuilder.build()); + jsonCategory.add(CategoryHelper.ACTIONS, jsonActions); + + TypeSpec.Builder connectorsTypeSpecBuilder = TypeSpec.classBuilder("Connectors").addModifiers(Modifier.PUBLIC, Modifier.STATIC); + JsonArray jsonConnectors = new JsonArray(); + Set connectorElements = roundEnv.getElementsAnnotatedWith(Connector.class); + for (Element connectorElement : connectorElements) { + Connector connector = connectorElement.getAnnotation(Connector.class); + String categoryId = category.id().isEmpty() ? categoryElement.getSimpleName().toString() : category.id(); + if (categoryId.equals(connector.categoryId())) { + Pair connectorResult = ConnectorProcessor.process(processor, roundEnv, pluginElement, plugin, categoryElement, category, connectorElement); + jsonConnectors.add(connectorResult.first); + connectorsTypeSpecBuilder.addType(connectorResult.second.build()); + } + } + categoryTypeSpecBuilder.addType(connectorsTypeSpecBuilder.build()); + jsonCategory.add(CategoryHelper.CONNECTORS, jsonConnectors); + + TypeSpec.Builder statesTypeSpecBuilder = TypeSpec.classBuilder("States").addModifiers(Modifier.PUBLIC, Modifier.STATIC); + JsonArray jsonStates = new JsonArray(); + Set stateElements = roundEnv.getElementsAnnotatedWith(State.class); + for (Element stateElement : stateElements) { + State state = stateElement.getAnnotation(State.class); + String categoryId = category.id().isEmpty() ? categoryElement.getSimpleName().toString() : category.id(); + if (categoryId.equals(state.categoryId())) { + Pair stateResult = StateProcessor.process(processor, roundEnv, pluginElement, plugin, categoryElement, category, stateElement); + jsonStates.add(stateResult.first); + statesTypeSpecBuilder.addType(stateResult.second.build()); + } + } + categoryTypeSpecBuilder.addType(statesTypeSpecBuilder.build()); + jsonCategory.add(CategoryHelper.STATES, jsonStates); + + TypeSpec.Builder eventsTypeSpecBuilder = TypeSpec.classBuilder("Events").addModifiers(Modifier.PUBLIC, Modifier.STATIC); + JsonArray jsonEvents = new JsonArray(); + Set eventElements = roundEnv.getElementsAnnotatedWith(Event.class); + for (Element eventElement : eventElements) { + State state = eventElement.getAnnotation(State.class); + String categoryId = category.id().isEmpty() ? categoryElement.getSimpleName().toString() : category.id(); + if (state != null) { + if (categoryId.equals(state.categoryId())) { + Pair eventResult = EventProcessor.process(processor, roundEnv, pluginElement, plugin, categoryElement, category, eventElement); + jsonEvents.add(eventResult.first); + eventsTypeSpecBuilder.addType(eventResult.second.build()); + } + } + else { + throw new TPAnnotationException.Builder(State.class).isMissing(true).forElement(eventElement).build(); + } + } + categoryTypeSpecBuilder.addType(eventsTypeSpecBuilder.build()); + jsonCategory.add(CategoryHelper.EVENTS, jsonEvents); + + return Pair.create(jsonCategory, categoryTypeSpecBuilder); + } +} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/ConnectorProcessor.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/ConnectorProcessor.java new file mode 100644 index 00000000..45388fb1 --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/ConnectorProcessor.java @@ -0,0 +1,81 @@ +package com.christophecvb.touchportal.annotations.processor; + +import com.christophecvb.touchportal.annotations.*; +import com.christophecvb.touchportal.annotations.processor.utils.Pair; +import com.christophecvb.touchportal.annotations.processor.utils.SpecUtils; +import com.christophecvb.touchportal.helpers.ConnectorHelper; +import com.christophecvb.touchportal.helpers.GenericHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.squareup.javapoet.TypeSpec; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.tools.Diagnostic; +import java.util.Set; + +public class ConnectorProcessor { + + /** + * Generates a JsonObject and a TypeSpec.Builder representing the {@link Connector} + * + * @param processor {@link TouchPortalPluginAnnotationsProcessor} + * @param roundEnv RoundEnvironment + * @param pluginElement Element + * @param plugin {@link Plugin} + * @param categoryElement Element + * @param category {@link Category} + * @param connectorElement Element + * @return Pair<JsonObject, TypeSpec.Builder> actionPair + * @throws GenericHelper.TPTypeException If a used type is not Supported + * @throws TPAnnotationException If an Annotation is misused + */ + public static Pair process(TouchPortalPluginAnnotationsProcessor processor, RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element connectorElement) throws GenericHelper.TPTypeException, TPAnnotationException { + processor.getMessager().printMessage(Diagnostic.Kind.NOTE, "Process Connector: " + connectorElement.getSimpleName()); + Connector connector = connectorElement.getAnnotation(Connector.class); + + TypeSpec.Builder connectorTypeSpecBuilder = SpecUtils.createConnectorTypeSpecBuilder(pluginElement, categoryElement, category, connectorElement, connector); + + JsonObject jsonConnector = new JsonObject(); + jsonConnector.addProperty(ConnectorHelper.ID, ConnectorHelper.getConnectorId(pluginElement, categoryElement, category, connectorElement, connector)); + jsonConnector.addProperty(ConnectorHelper.NAME, ConnectorHelper.getConnectorName(connectorElement, connector)); + jsonConnector.addProperty(ConnectorHelper.FORMAT, connector.format()); + + String classMethod = pluginElement.getSimpleName() + "." + connectorElement.getSimpleName(); + boolean connectorValueFound = false; + Set connectorValueElements = roundEnv.getElementsAnnotatedWith(ConnectorValue.class); + for (Element connectorValueElement : connectorValueElements) { + Element enclosingElement = connectorValueElement.getEnclosingElement(); + if (connectorElement.equals(enclosingElement)) { + String classMethodParameter = classMethod + "(" + connectorValueElement.getSimpleName() + ")"; + String desiredType = GenericHelper.getTouchPortalType(classMethodParameter, connectorValueElement); + if (!desiredType.equals(GenericHelper.TP_TYPE_NUMBER)) { + throw new GenericHelper.TPTypeException.Builder(classMethodParameter).typeUnsupported(desiredType).forAnnotation(GenericHelper.TPTypeException.ForAnnotation.CONNECTOR_VALUE).build(); + } + connectorValueFound = true; + break; + } + } + if (!connectorValueFound) { + throw new TPAnnotationException.Builder(ConnectorValue.class).isMissing(true).forElement(connectorElement).build(); + } + + JsonArray jsonConnectorData = new JsonArray(); + Set dataElements = roundEnv.getElementsAnnotatedWith(Data.class); + for (Element dataElement : dataElements) { + Element enclosingElement = dataElement.getEnclosingElement(); + if (connectorElement.equals(enclosingElement)) { + Pair connectorDataResult = DataProcessor.process(processor, roundEnv, pluginElement, plugin, categoryElement, category, connectorElement, connector, jsonConnector, dataElement); + jsonConnectorData.add(connectorDataResult.first); + if (connectorDataResult.second != null) { + connectorTypeSpecBuilder.addType(connectorDataResult.second.build()); + } + } + } + if (jsonConnectorData.size() > 0) { + jsonConnector.add(ConnectorHelper.DATA, jsonConnectorData); + } + + return Pair.create(jsonConnector, connectorTypeSpecBuilder); + } +} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/DataProcessor.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/DataProcessor.java new file mode 100644 index 00000000..7d8af7ce --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/DataProcessor.java @@ -0,0 +1,169 @@ +package com.christophecvb.touchportal.annotations.processor; + +import com.christophecvb.touchportal.annotations.*; +import com.christophecvb.touchportal.annotations.processor.utils.Pair; +import com.christophecvb.touchportal.annotations.processor.utils.SpecUtils; +import com.christophecvb.touchportal.helpers.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.squareup.javapoet.TypeSpec; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.tools.Diagnostic; +import java.lang.annotation.Annotation; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +public class DataProcessor { + /** + * Generates a JsonObject and a TypeSpec.Builder representing the {@link Data} for an {@link Action} or a {@link Connector} + * + * @param Action or Connector Annotation + * @param processor {@link TouchPortalPluginAnnotationsProcessor} + * @param roundEnv RoundEnvironment + * @param pluginElement Element + * @param plugin {@link Plugin} + * @param categoryElement Element + * @param category {@link Category} + * @param targetElement Element + * @param annotation {@link Action} or {@link Connector} + * @param jsonElement JsonObject + * @param dataElement Element + * @return Pair<JsonObject, TypeSpec.Builder> dataPair + * @throws GenericHelper.TPTypeException If a used type is not Supported + */ + public static Pair process(TouchPortalPluginAnnotationsProcessor processor, RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element targetElement, T annotation, JsonObject jsonElement, Element dataElement) throws GenericHelper.TPTypeException { + String annotationName = annotation.annotationType().getSimpleName(); + boolean isAction = annotation instanceof Action; + + processor.getMessager().printMessage(Diagnostic.Kind.NOTE, "Process " + annotationName + " Data: " + dataElement.getSimpleName()); + Data data = dataElement.getAnnotation(Data.class); + + TypeSpec.Builder dataTypeSpecBuilder = isAction ? + SpecUtils.createActionDataTypeSpecBuilder(pluginElement, categoryElement, category, targetElement, (Action) annotation, dataElement, data) : + SpecUtils.createConnectorDataTypeSpecBuilder(pluginElement, categoryElement, category, targetElement, (Connector) annotation, dataElement, data); + + Element method = dataElement.getEnclosingElement(); + String className = method.getEnclosingElement().getSimpleName() + "." + annotationName + "(" + dataElement.getSimpleName() + ")"; + + JsonObject jsonData = new JsonObject(); + String desiredTPType = GenericHelper.getTouchPortalType(className, dataElement); + jsonData.addProperty(DataHelper.TYPE, desiredTPType); + jsonData.addProperty(DataHelper.LABEL, DataHelper.getDataLabel(dataElement, data)); + // Default Value + switch (desiredTPType) { + case GenericHelper.TP_TYPE_NUMBER: + double defaultValue = 0; + try { + defaultValue = Double.parseDouble(data.defaultValue()); + } + catch (NumberFormatException ignored) {} + jsonData.addProperty(DataHelper.DEFAULT, defaultValue); + break; + + case GenericHelper.TP_TYPE_SWITCH: + jsonData.addProperty(DataHelper.DEFAULT, data.defaultValue().equals("true")); + break; + + default: + jsonData.addProperty(DataHelper.DEFAULT, data.defaultValue()); + break; + } + AtomicReference dataId = new AtomicReference<>( isAction ? + DataHelper.getActionDataId(pluginElement, categoryElement, category, targetElement, (Action) annotation, dataElement, data) : + DataHelper.getConnectorDataId(pluginElement, categoryElement, category, targetElement, (Connector) annotation, dataElement, data)); + // Specific properties + switch (desiredTPType) { + case GenericHelper.TP_TYPE_CHOICE: + JsonArray dataValueChoices = new JsonArray(); + if (!data.stateId().isEmpty()) { + Optional optionalStateElement = roundEnv.getElementsAnnotatedWith(State.class).stream().filter(element -> { + State state = element.getAnnotation(State.class); + String shortStateId = !state.id().isEmpty() ? state.id() : element.getSimpleName().toString(); + return shortStateId.equals(data.stateId()); + }).findFirst(); + if (optionalStateElement.isPresent()) { + dataTypeSpecBuilder = null; + + Element stateElement = optionalStateElement.get(); + State state = stateElement.getAnnotation(State.class); + dataId.set(StateHelper.getStateId(pluginElement, categoryElement, category, stateElement, state)); + for (String valueChoice : state.valueChoices()) { + dataValueChoices.add(valueChoice); + } + jsonData.addProperty(DataHelper.DEFAULT, data.defaultValue().isEmpty() ? state.defaultValue() : data.defaultValue()); + } + else { + for (String valueChoice : data.valueChoices()) { + dataValueChoices.add(valueChoice); + } + } + } + else { + for (String valueChoice : data.valueChoices()) { + dataValueChoices.add(valueChoice); + } + } + jsonData.add(DataHelper.VALUE_CHOICES, dataValueChoices); + break; + + case GenericHelper.TP_TYPE_FILE: + if (data.isDirectory()) { + jsonData.addProperty(DataHelper.TYPE, GenericHelper.TP_TYPE_DIRECTORY); + } + else { + JsonArray jsonExtensions = new JsonArray(); + for (String extension : data.extensions()) { + if (extension.matches(DataHelper.EXTENSION_FORMAT)) { + jsonExtensions.add(extension); + } + else { + processor.getMessager().printMessage(Diagnostic.Kind.ERROR, annotationName + " Data Extension: [" + extension + "] format is not valid"); + } + } + jsonData.add(DataHelper.EXTENSIONS, jsonExtensions); + } + break; + + case GenericHelper.TP_TYPE_TEXT: + if (data.isColor()) { + jsonData.addProperty(DataHelper.TYPE, GenericHelper.TP_TYPE_COLOR); + if (!data.defaultValue().isEmpty()) { + if (!data.defaultValue().matches(DataHelper.COLOR_FORMAT)) { + processor.getMessager().printMessage(Diagnostic.Kind.ERROR, annotationName + " Data Color Default value: [" + data.defaultValue() + "] format is not valid"); + } + } + } + break; + + case GenericHelper.TP_TYPE_NUMBER: + try { + double defaultValue = jsonData.get(DataHelper.DEFAULT).getAsDouble(); + if (defaultValue < data.minValue() || defaultValue > data.maxValue()) { + throw new GenericHelper.TPTypeException.Builder(className).defaultNotInRange().build(); + } + } + catch (NumberFormatException numberFormatException) { + throw new GenericHelper.TPTypeException.Builder(className).defaultInvalid(data.defaultValue()).build(); + } + jsonData.addProperty(DataHelper.ALLOW_DECIMALS, GenericHelper.getTouchPortalTypeNumberAllowDecimals(dataElement.asType().toString())); + if (data.minValue() > Double.NEGATIVE_INFINITY) { + jsonData.addProperty(DataHelper.MIN_VALUE, data.minValue()); + } + if (data.maxValue() < Double.POSITIVE_INFINITY) { + jsonData.addProperty(DataHelper.MAX_VALUE, data.maxValue()); + } + break; + } + jsonData.addProperty(DataHelper.ID, dataId.get()); + String format = isAction ? ((Action) annotation).format() : ((Connector) annotation).format(); + if (!format.isEmpty()) { + // Replace wildcards + String rawFormat = jsonElement.get(isAction ? ActionHelper.FORMAT : ConnectorHelper.FORMAT).getAsString(); + jsonElement.addProperty(isAction ? ActionHelper.FORMAT : ConnectorHelper.FORMAT, rawFormat.replace("{$" + (data.id().isEmpty() ? dataElement.getSimpleName().toString() : data.id()) + "$}", "{$" + dataId.get() + "$}")); + } + + return Pair.create(jsonData, dataTypeSpecBuilder); + } +} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/EventProcessor.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/EventProcessor.java new file mode 100644 index 00000000..f5d7a44d --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/EventProcessor.java @@ -0,0 +1,69 @@ +package com.christophecvb.touchportal.annotations.processor; + +import com.christophecvb.touchportal.annotations.Category; +import com.christophecvb.touchportal.annotations.Event; +import com.christophecvb.touchportal.annotations.Plugin; +import com.christophecvb.touchportal.annotations.State; +import com.christophecvb.touchportal.annotations.processor.utils.Pair; +import com.christophecvb.touchportal.annotations.processor.utils.SpecUtils; +import com.christophecvb.touchportal.helpers.EventHelper; +import com.christophecvb.touchportal.helpers.GenericHelper; +import com.christophecvb.touchportal.helpers.StateHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.squareup.javapoet.TypeSpec; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.tools.Diagnostic; + +public class EventProcessor { + /** + * Generates a JsonObject and a TypeSpec.Builder representing the {@link Event} + * + * @param processor {@link TouchPortalPluginAnnotationsProcessor} + * @param roundEnv RoundEnvironment + * @param pluginElement Element + * @param plugin {@link Plugin} + * @param categoryElement Element + * @param category {@link Category} + * @param eventElement Element + * @return Pair<JsonObject, TypeSpec.Builder> eventPair + * @throws GenericHelper.TPTypeException If a used type is not Supported + * @throws TPAnnotationException If an Annotation is misused + */ + public static Pair process(TouchPortalPluginAnnotationsProcessor processor, RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element eventElement) throws GenericHelper.TPTypeException, TPAnnotationException { + processor.getMessager().printMessage(Diagnostic.Kind.NOTE, "Process Event: " + eventElement.getSimpleName()); + State state = eventElement.getAnnotation(State.class); + Event event = eventElement.getAnnotation(Event.class); + + String reference = eventElement.getEnclosingElement().getSimpleName() + "." + eventElement.getSimpleName(); + + if (state == null) { + throw new TPAnnotationException.Builder(State.class).isMissing(true).forElement(eventElement).build(); + } + + TypeSpec.Builder eventTypeSpecBuilder = SpecUtils.createEventTypeSpecBuilder(pluginElement, categoryElement, category, eventElement, event); + + JsonObject jsonEvent = new JsonObject(); + jsonEvent.addProperty(EventHelper.ID, EventHelper.getEventId(pluginElement, categoryElement, category, eventElement, event)); + jsonEvent.addProperty(EventHelper.TYPE, EventHelper.TYPE_COMMUNICATE); + jsonEvent.addProperty(EventHelper.NAME, EventHelper.getEventName(eventElement, event)); + jsonEvent.addProperty(EventHelper.FORMAT, event.format()); + String desiredTPType = GenericHelper.getTouchPortalType(reference, eventElement); + if (desiredTPType.equals(StateHelper.TYPE_TEXT)) { + jsonEvent.addProperty(EventHelper.VALUE_TYPE, EventHelper.VALUE_TYPE_CHOICE); + JsonArray eventValueChoices = new JsonArray(); + for (String valueChoice : event.valueChoices()) { + eventValueChoices.add(valueChoice); + } + jsonEvent.add(EventHelper.VALUE_CHOICES, eventValueChoices); + jsonEvent.addProperty(EventHelper.VALUE_STATE_ID, StateHelper.getStateId(pluginElement, categoryElement, category, eventElement, state)); + } + else { + throw new GenericHelper.TPTypeException.Builder(reference).typeUnsupported(desiredTPType).forAnnotation(GenericHelper.TPTypeException.ForAnnotation.EVENT).build(); + } + + return Pair.create(jsonEvent, eventTypeSpecBuilder); + } +} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/PluginProcessor.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/PluginProcessor.java new file mode 100644 index 00000000..f68dcde0 --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/PluginProcessor.java @@ -0,0 +1,81 @@ +package com.christophecvb.touchportal.annotations.processor; + +import com.christophecvb.touchportal.annotations.Category; +import com.christophecvb.touchportal.annotations.Plugin; +import com.christophecvb.touchportal.annotations.Setting; +import com.christophecvb.touchportal.annotations.processor.utils.Pair; +import com.christophecvb.touchportal.annotations.processor.utils.SpecUtils; +import com.christophecvb.touchportal.helpers.GenericHelper; +import com.christophecvb.touchportal.helpers.PluginHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.squareup.javapoet.TypeSpec; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.tools.Diagnostic; +import java.util.Set; + +public class PluginProcessor { + /** + * Generates a JsonObject and a TypeSpec.Builder representing the {@link Plugin} + * + * @param processor {@link TouchPortalPluginAnnotationsProcessor} + * @param roundEnv RoundEnvironment + * @param pluginElement Element + * @return Pair<JsonObject, TypeSpec.Builder> pluginPair + * @throws GenericHelper.TPTypeException If a used type is not Supported + * @throws TPAnnotationException If an Annotation is misused + */ + public static Pair process(TouchPortalPluginAnnotationsProcessor processor, RoundEnvironment roundEnv, Element pluginElement) throws GenericHelper.TPTypeException, TPAnnotationException { + processor.getMessager().printMessage(Diagnostic.Kind.NOTE, "Process Plugin: " + pluginElement.getSimpleName()); + Plugin plugin = pluginElement.getAnnotation(Plugin.class); + + TypeSpec.Builder pluginTypeSpecBuilder = SpecUtils.createPluginTypeSpecBuilder(pluginElement, plugin); + + JsonObject jsonPlugin = new JsonObject(); + jsonPlugin.addProperty(PluginHelper.API, PluginHelper.TOUCH_PORTAL_PLUGIN_VERSION); + jsonPlugin.addProperty(PluginHelper.VERSION, plugin.version()); + jsonPlugin.addProperty(PluginHelper.NAME, PluginHelper.getPluginName(pluginElement, plugin)); + jsonPlugin.addProperty(PluginHelper.ID, PluginHelper.getPluginId(pluginElement)); + JsonObject jsonConfiguration = new JsonObject(); + jsonConfiguration.addProperty(PluginHelper.CONFIGURATION_COLOR_DARK, plugin.colorDark()); + jsonConfiguration.addProperty(PluginHelper.CONFIGURATION_COLOR_LIGHT, plugin.colorLight()); + jsonConfiguration.addProperty(PluginHelper.CONFIGURATION_PARENT_CATEGORY, plugin.parentCategory().getKey()); + jsonPlugin.add(PluginHelper.CONFIGURATION, jsonConfiguration); + jsonPlugin.addProperty(PluginHelper.PLUGIN_START_COMMAND, "java -Dapple.awt.UIElement=true -jar ./" + pluginElement.getSimpleName() + ".jar " + PluginHelper.COMMAND_START); + jsonPlugin.addProperty(PluginHelper.PLUGIN_START_COMMAND + PluginHelper.PLUGIN_START_COMMAND_SUFFIX_WIN, "java -jar ./" + pluginElement.getSimpleName() + ".jar " + PluginHelper.COMMAND_START); + jsonPlugin.addProperty(PluginHelper.PLUGIN_START_COMMAND + PluginHelper.PLUGIN_START_COMMAND_SUFFIX_MACOS, "java -Dapple.awt.UIElement=true -jar ./" + pluginElement.getSimpleName() + ".jar " + PluginHelper.COMMAND_START); + jsonPlugin.addProperty(PluginHelper.PLUGIN_START_COMMAND + PluginHelper.PLUGIN_START_COMMAND_SUFFIX_LINUX, "java -jar ./" + pluginElement.getSimpleName() + ".jar " + PluginHelper.COMMAND_START); + + TypeSpec.Builder settingsTypeSpecBuilder = TypeSpec.classBuilder("Settings").addModifiers(Modifier.PUBLIC, Modifier.STATIC); + JsonArray jsonSettings = new JsonArray(); + Set settingElements = roundEnv.getElementsAnnotatedWith(Setting.class); + for (Element settingElement : settingElements) { + Pair settingResult = SettingProcessor.process(processor, settingElement); + jsonSettings.add(settingResult.first); + settingsTypeSpecBuilder.addType(settingResult.second.build()); + } + if (jsonSettings.size() > 0) { + jsonPlugin.add(PluginHelper.SETTINGS, jsonSettings); + } + pluginTypeSpecBuilder.addType(settingsTypeSpecBuilder.build()); + + JsonArray jsonCategories = new JsonArray(); + Set categoryElements = roundEnv.getElementsAnnotatedWith(Category.class); + for (Element categoryElement : categoryElements) { + Pair categoryResult = CategoryProcessor.process(processor, roundEnv, pluginElement, plugin, categoryElement); + jsonCategories.add(categoryResult.first); + pluginTypeSpecBuilder.addType(categoryResult.second.build()); + } + if (jsonCategories.size() > 0) { + jsonPlugin.add(PluginHelper.CATEGORIES, jsonCategories); + } + else { + throw new TPAnnotationException.Builder(Category.class).isMissing(true).build(); + } + + return Pair.create(jsonPlugin, pluginTypeSpecBuilder); + } +} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/SettingProcessor.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/SettingProcessor.java new file mode 100644 index 00000000..6e27f229 --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/SettingProcessor.java @@ -0,0 +1,87 @@ +package com.christophecvb.touchportal.annotations.processor; + +import com.christophecvb.touchportal.annotations.Setting; +import com.christophecvb.touchportal.annotations.processor.utils.Pair; +import com.christophecvb.touchportal.annotations.processor.utils.SpecUtils; +import com.christophecvb.touchportal.helpers.GenericHelper; +import com.christophecvb.touchportal.helpers.SettingHelper; +import com.google.gson.JsonObject; +import com.squareup.javapoet.TypeSpec; + +import javax.lang.model.element.Element; +import javax.tools.Diagnostic; + +public class SettingProcessor { + /** + * Generates a JsonObject and a TypeSpec.Builder representing the {@link Setting} + * + * @param processor {@link TouchPortalPluginAnnotationsProcessor} + * @param settingElement Element + * @return Pair<JsonObject, TypeSpec.Builder> statePair + * @throws GenericHelper.TPTypeException If a used type is not Supported + */ + public static Pair process(TouchPortalPluginAnnotationsProcessor processor, Element settingElement) throws GenericHelper.TPTypeException { + processor.getMessager().printMessage(Diagnostic.Kind.NOTE, "Process Setting: " + settingElement.getSimpleName()); + Setting setting = settingElement.getAnnotation(Setting.class); + + TypeSpec.Builder settingTypeSpecBuilder = SpecUtils.createSettingTypeSpecBuilder(settingElement, setting); + + String className = settingElement.getEnclosingElement().getSimpleName() + "." + settingElement.getSimpleName(); + + JsonObject jsonSetting = new JsonObject(); + jsonSetting.addProperty(SettingHelper.NAME, SettingHelper.getSettingName(settingElement, setting)); + String desiredTPType = GenericHelper.getTouchPortalType(className, settingElement); + jsonSetting.addProperty(SettingHelper.TYPE, desiredTPType); + jsonSetting.addProperty(SettingHelper.DEFAULT, setting.defaultValue()); + jsonSetting.addProperty(SettingHelper.IS_READ_ONLY, setting.isReadOnly()); + + if (!setting.tooltip().body().isEmpty()) { + JsonObject tooltip = new JsonObject(); + + tooltip.addProperty(SettingHelper.Tooltip.BODY, setting.tooltip().body()); + + if (!setting.tooltip().title().isEmpty()) { + tooltip.addProperty(SettingHelper.Tooltip.TITLE, setting.tooltip().title()); + } + if (!setting.tooltip().docUrl().isEmpty()) { + tooltip.addProperty(SettingHelper.Tooltip.DOC_URL, setting.tooltip().docUrl()); + } + + jsonSetting.add(SettingHelper.TOOLTIP, tooltip); + } + + switch (desiredTPType) { + case SettingHelper.TYPE_TEXT: + if (setting.maxLength() > 0) { + jsonSetting.addProperty(SettingHelper.MAX_LENGTH, setting.maxLength()); + } + if (setting.isPassword()) { + jsonSetting.addProperty(SettingHelper.IS_PASSWORD, true); + } + break; + + case SettingHelper.TYPE_NUMBER: + try { + double defaultValue = Double.parseDouble(setting.defaultValue()); + if (defaultValue < setting.minValue() || defaultValue > setting.maxValue()) { + throw new GenericHelper.TPTypeException.Builder(className).defaultNotInRange().build(); + } + } + catch (NumberFormatException numberFormatException) { + throw new GenericHelper.TPTypeException.Builder(className).defaultInvalid(setting.defaultValue()).build(); + } + if (setting.minValue() > Double.NEGATIVE_INFINITY) { + jsonSetting.addProperty(SettingHelper.MIN_VALUE, setting.minValue()); + } + if (setting.maxValue() < Double.POSITIVE_INFINITY) { + jsonSetting.addProperty(SettingHelper.MAX_VALUE, setting.maxValue()); + } + break; + + default: + throw new GenericHelper.TPTypeException.Builder(className).typeUnsupported(desiredTPType).forAnnotation(GenericHelper.TPTypeException.ForAnnotation.SETTING).build(); + } + + return Pair.create(jsonSetting, settingTypeSpecBuilder); + } +} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/StateProcessor.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/StateProcessor.java new file mode 100644 index 00000000..2e398027 --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/StateProcessor.java @@ -0,0 +1,66 @@ +package com.christophecvb.touchportal.annotations.processor; + +import com.christophecvb.touchportal.annotations.Category; +import com.christophecvb.touchportal.annotations.Event; +import com.christophecvb.touchportal.annotations.Plugin; +import com.christophecvb.touchportal.annotations.State; +import com.christophecvb.touchportal.annotations.processor.utils.Pair; +import com.christophecvb.touchportal.annotations.processor.utils.SpecUtils; +import com.christophecvb.touchportal.helpers.GenericHelper; +import com.christophecvb.touchportal.helpers.StateHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.squareup.javapoet.TypeSpec; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.tools.Diagnostic; + +public class StateProcessor { + /** + * Generates a JsonObject and a TypeSpec.Builder representing the {@link State} + * + * @param processor {@link TouchPortalPluginAnnotationsProcessor} + * @param roundEnv RoundEnvironment + * @param pluginElement Element + * @param plugin {@link Plugin} + * @param categoryElement Element + * @param category {@link Category} + * @param stateElement Element + * @return Pair<JsonObject, TypeSpec.Builder> statePair + * @throws GenericHelper.TPTypeException If a used type is not Supported + * @throws TPAnnotationException If an Annotation is misused + */ + public static Pair process(TouchPortalPluginAnnotationsProcessor processor, RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element stateElement) throws GenericHelper.TPTypeException, TPAnnotationException { + processor.getMessager().printMessage(Diagnostic.Kind.NOTE, "Process State: " + stateElement.getSimpleName()); + State state = stateElement.getAnnotation(State.class); + + TypeSpec.Builder stateTypeSpecBuilder = SpecUtils.createStateTypeSpecBuilder(pluginElement, categoryElement, category, stateElement, state); + + String className = stateElement.getEnclosingElement().getSimpleName() + "." + stateElement.getSimpleName(); + + JsonObject jsonState = new JsonObject(); + jsonState.addProperty(StateHelper.ID, StateHelper.getStateId(pluginElement, categoryElement, category, stateElement, state)); + String desiredTPType = GenericHelper.getTouchPortalType(className, stateElement); + jsonState.addProperty(StateHelper.TYPE, desiredTPType); + jsonState.addProperty(StateHelper.DESC, StateHelper.getStateDesc(stateElement, state)); + jsonState.addProperty(StateHelper.DEFAULT, state.defaultValue()); + if (desiredTPType.equals(StateHelper.TYPE_CHOICE)) { + JsonArray stateValueChoices = new JsonArray(); + for (String valueChoice : state.valueChoices()) { + stateValueChoices.add(valueChoice); + } + jsonState.add(StateHelper.VALUE_CHOICES, stateValueChoices); + } + else if (!desiredTPType.equals(StateHelper.TYPE_TEXT)) { + throw new GenericHelper.TPTypeException.Builder(className).typeUnsupported(desiredTPType).forAnnotation(GenericHelper.TPTypeException.ForAnnotation.STATE).build(); + } + + Event event = stateElement.getAnnotation(Event.class); + if (event != null && !desiredTPType.equals(StateHelper.TYPE_TEXT)) { + throw new TPAnnotationException.Builder(State.class).typeFor(desiredTPType, className, "the field is also Annotated with Event. Only the type " + StateHelper.TYPE_TEXT + " is supported for a State that has an Event Annotation.").build(); + } + + return Pair.create(jsonState, stateTypeSpecBuilder); + } +} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/TPAnnotationException.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/TPAnnotationException.java new file mode 100644 index 00000000..c680bbf4 --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/TPAnnotationException.java @@ -0,0 +1,51 @@ +package com.christophecvb.touchportal.annotations.processor; + +import javax.lang.model.element.Element; +import java.lang.annotation.Annotation; + +public class TPAnnotationException extends Exception { + private TPAnnotationException(Class annotationType, Boolean isMissing, Integer count, Element element, String typeFor) { + super(annotationType.getSimpleName() + " Annotation" + + (isMissing != null && isMissing ? " is missing" : "") + + (count != null ? " count cannot be " + count : "") + + (typeFor != null ? " " + typeFor : "") + + (element != null ? " for element " + element.getSimpleName() : "") + ); + } + + public static class Builder { + private final Class annotationType; + private Boolean isMissing; + private Integer count; + private Element element; + private String typeFor; + + public Builder(Class annotationType) { + this.annotationType = annotationType; + } + + public Builder isMissing(Boolean isMissing) { + this.isMissing = isMissing; + return this; + } + + public Builder forElement(Element element) { + this.element = element; + return this; + } + + public Builder typeFor(String type, String forElement, String because) { + this.typeFor = "type for " + forElement + " cannot be " + type + " because " + because; + return this; + } + + public Builder count(int count) { + this.count = count; + return this; + } + + public TPAnnotationException build() { + return new TPAnnotationException(this.annotationType, this.isMissing, this.count, this.element, this.typeFor); + } + } +} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/TouchPortalPluginAnnotationProcessor.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/TouchPortalPluginAnnotationProcessor.java deleted file mode 100644 index 08ac3d90..00000000 --- a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/TouchPortalPluginAnnotationProcessor.java +++ /dev/null @@ -1,1091 +0,0 @@ -/* - * Touch Portal Plugin SDK - * - * Copyright 2020 Christophe Carvalho Vilas-Boas - * christophe.carvalhovilasboas@gmail.com - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.christophecvb.touchportal.annotations.processor; - -import com.christophecvb.touchportal.annotations.*; -import com.christophecvb.touchportal.annotations.processor.utils.Pair; -import com.christophecvb.touchportal.helpers.*; -import com.google.auto.service.AutoService; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.squareup.javapoet.ArrayTypeName; -import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.TypeSpec; - -import javax.annotation.processing.*; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; -import javax.tools.FileObject; -import javax.tools.StandardLocation; -import java.io.Writer; -import java.lang.annotation.AnnotationFormatError; -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Touch Portal Plugin Annotation Processor - */ -@AutoService(Processor.class) -public class TouchPortalPluginAnnotationProcessor extends AbstractProcessor { - private Filer filer; - private Messager messager; - - public static String capitalize(String str) { - if (str == null || str.isEmpty()) { - return str; - } - - return str.substring(0, 1).toUpperCase() + str.substring(1); - } - - @Override - public synchronized void init(ProcessingEnvironment processingEnv) { - super.init(processingEnv); - this.filer = processingEnv.getFiler(); - this.messager = processingEnv.getMessager(); - } - - @Override - public Set getSupportedAnnotationTypes() { - Set annotations = new LinkedHashSet<>(); - annotations.add(Plugin.class.getCanonicalName()); - annotations.add(Setting.class.getCanonicalName()); - annotations.add(Category.class.getCanonicalName()); - annotations.add(Action.class.getCanonicalName()); - annotations.add(Data.class.getCanonicalName()); - annotations.add(State.class.getCanonicalName()); - annotations.add(Event.class.getCanonicalName()); - annotations.add(Connector.class.getCanonicalName()); - return annotations; - } - - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - if (roundEnv.processingOver() || annotations.size() == 0) { - return false; - } - this.messager.printMessage(Diagnostic.Kind.NOTE, this.getClass().getSimpleName() + ".process"); - - try { - Set plugins = roundEnv.getElementsAnnotatedWith(Plugin.class); - if (plugins.size() != 1) { - throw new Exception("You need 1(one) @Plugin Annotation, you have " + plugins.size()); - } - for (Element pluginElement : plugins) { - Pair pluginPair = this.processPlugin(roundEnv, pluginElement); - - String actionFileName = "resources/" + PluginHelper.ENTRY_TP; - FileObject actionFileObject = this.filer.createResource(StandardLocation.SOURCE_OUTPUT, "", actionFileName, pluginElement); - Writer writer = actionFileObject.openWriter(); - writer.write(pluginPair.first.toString()); - writer.flush(); - writer.close(); - - TypeSpec pluginTypeSpec = pluginPair.second.build(); - String packageName = ((PackageElement) pluginElement.getEnclosingElement()).getQualifiedName().toString(); - JavaFile javaConstantsFile = JavaFile.builder(packageName, pluginTypeSpec).build(); - javaConstantsFile.writeTo(this.filer); - } - } - catch (Exception exception) { - this.messager.printMessage(Diagnostic.Kind.ERROR, exception.getMessage()); - } - - return true; - } - - /** - * Generates a JsonObject and a TypeSpec.Builder representing the {@link Plugin} - * - * @param roundEnv RoundEnvironment - * @param pluginElement Element - * @return Pair pluginPair - * @throws GenericHelper.TPTypeException If a used type is not Supported - */ - private Pair processPlugin(RoundEnvironment roundEnv, Element pluginElement) throws Exception { - this.messager.printMessage(Diagnostic.Kind.NOTE, "Process Plugin: " + pluginElement.getSimpleName()); - Plugin plugin = pluginElement.getAnnotation(Plugin.class); - - TypeSpec.Builder pluginTypeSpecBuilder = this.createPluginTypeSpecBuilder(pluginElement, plugin); - - JsonObject jsonPlugin = new JsonObject(); - jsonPlugin.addProperty(PluginHelper.SDK, PluginHelper.TOUCH_PORTAL_PLUGIN_VERSION); - jsonPlugin.addProperty(PluginHelper.VERSION, plugin.version()); - jsonPlugin.addProperty(PluginHelper.NAME, PluginHelper.getPluginName(pluginElement, plugin)); - jsonPlugin.addProperty(PluginHelper.ID, PluginHelper.getPluginId(pluginElement)); - JsonObject jsonConfiguration = new JsonObject(); - jsonConfiguration.addProperty(PluginHelper.CONFIGURATION_COLOR_DARK, plugin.colorDark()); - jsonConfiguration.addProperty(PluginHelper.CONFIGURATION_COLOR_LIGHT, plugin.colorLight()); - jsonPlugin.add(PluginHelper.CONFIGURATION, jsonConfiguration); - jsonPlugin.addProperty(PluginHelper.PLUGIN_START_COMMAND, "java -Dapple.awt.UIElement=true -jar ./" + pluginElement.getSimpleName() + ".jar " + PluginHelper.COMMAND_START); - - TypeSpec.Builder settingsTypeSpecBuilder = TypeSpec.classBuilder("Settings").addModifiers(Modifier.PUBLIC, Modifier.STATIC); - JsonArray jsonSettings = new JsonArray(); - Set settingElements = roundEnv.getElementsAnnotatedWith(Setting.class); - for (Element settingElement : settingElements) { - Pair settingResult = this.processSetting(settingElement); - jsonSettings.add(settingResult.first); - settingsTypeSpecBuilder.addType(settingResult.second.build()); - } - if (jsonSettings.size() > 0) { - jsonPlugin.add(PluginHelper.SETTINGS, jsonSettings); - } - pluginTypeSpecBuilder.addType(settingsTypeSpecBuilder.build()); - - JsonArray jsonCategories = new JsonArray(); - Set categoryElements = roundEnv.getElementsAnnotatedWith(Category.class); - for (Element categoryElement : categoryElements) { - Pair categoryResult = this.processCategory(roundEnv, pluginElement, plugin, categoryElement); - jsonCategories.add(categoryResult.first); - pluginTypeSpecBuilder.addType(categoryResult.second.build()); - } - if (jsonCategories.size() > 0) { - jsonPlugin.add(PluginHelper.CATEGORIES, jsonCategories); - } - else { - throw new Exception("Category Annotation missing"); - } - - return Pair.create(jsonPlugin, pluginTypeSpecBuilder); - } - - /** - * Generates a JsonObject and a TypeSpec.Builder representing the {@link Setting} - * - * @param settingElement Element - * @return Pair statePair - * @throws GenericHelper.TPTypeException If a used type is not Supported - */ - private Pair processSetting(Element settingElement) throws Exception { - this.messager.printMessage(Diagnostic.Kind.NOTE, "Process Setting: " + settingElement.getSimpleName()); - Setting setting = settingElement.getAnnotation(Setting.class); - - TypeSpec.Builder settingTypeSpecBuilder = this.createSettingTypeSpecBuilder(settingElement, setting); - - String className = settingElement.getEnclosingElement().getSimpleName() + "." + settingElement.getSimpleName(); - - JsonObject jsonSetting = new JsonObject(); - jsonSetting.addProperty(SettingHelper.NAME, SettingHelper.getSettingName(settingElement, setting)); - String desiredTPType = GenericHelper.getTouchPortalType(className, settingElement); - jsonSetting.addProperty(SettingHelper.TYPE, desiredTPType); - jsonSetting.addProperty(SettingHelper.DEFAULT, setting.defaultValue()); - jsonSetting.addProperty(SettingHelper.IS_READ_ONLY, setting.isReadOnly()); - switch (desiredTPType) { - case SettingHelper.TYPE_TEXT: - if (setting.maxLength() > 0) { - jsonSetting.addProperty(SettingHelper.MAX_LENGTH, setting.maxLength()); - } - if (setting.isPassword()) { - jsonSetting.addProperty(SettingHelper.IS_PASSWORD, true); - } - break; - - case SettingHelper.TYPE_NUMBER: - try { - double defaultValue = Double.parseDouble(setting.defaultValue()); - if (defaultValue < setting.minValue() || defaultValue > setting.maxValue()) { - throw new GenericHelper.TPTypeException.Builder(className).defaultNotInRange().build(); - } - } - catch (NumberFormatException numberFormatException) { - throw new GenericHelper.TPTypeException.Builder(className).defaultInvalid(setting.defaultValue()).build(); - } - if (setting.minValue() > Double.NEGATIVE_INFINITY) { - jsonSetting.addProperty(SettingHelper.MIN_VALUE, setting.minValue()); - } - if (setting.maxValue() < Double.POSITIVE_INFINITY) { - jsonSetting.addProperty(SettingHelper.MAX_VALUE, setting.maxValue()); - } - break; - - default: - throw new GenericHelper.TPTypeException.Builder(className).typeUnsupported(desiredTPType).forAnnotation(GenericHelper.TPTypeException.ForAnnotation.SETTING).build(); - } - - return Pair.create(jsonSetting, settingTypeSpecBuilder); - } - - /** - * Generates a JsonObject and a TypeSpec.Builder representing the {@link Category} - * - * @param roundEnv RoundEnvironment - * @param pluginElement Element - * @param plugin {@link Plugin} - * @param categoryElement Element - * @return Pair categoryPair - * @throws GenericHelper.TPTypeException If a used type is not Supported - */ - private Pair processCategory(RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement) throws Exception { - this.messager.printMessage(Diagnostic.Kind.NOTE, "Process Category: " + categoryElement.getSimpleName()); - Category category = categoryElement.getAnnotation(Category.class); - - TypeSpec.Builder categoryTypeSpecBuilder = this.createCategoryTypeSpecBuilder(pluginElement, categoryElement, category); - - JsonObject jsonCategory = new JsonObject(); - jsonCategory.addProperty(CategoryHelper.ID, CategoryHelper.getCategoryId(pluginElement, categoryElement, category)); - jsonCategory.addProperty(CategoryHelper.NAME, CategoryHelper.getCategoryName(categoryElement, category)); - jsonCategory.addProperty(CategoryHelper.IMAGE_PATH, PluginHelper.TP_PLUGIN_FOLDER + pluginElement.getSimpleName() + "/" + category.imagePath()); - - TypeSpec.Builder actionsTypeSpecBuilder = TypeSpec.classBuilder("Actions").addModifiers(Modifier.PUBLIC, Modifier.STATIC); - JsonArray jsonActions = new JsonArray(); - Set actionElements = roundEnv.getElementsAnnotatedWith(Action.class); - for (Element actionElement : actionElements) { - Action action = actionElement.getAnnotation(Action.class); - String categoryId = category.id().isEmpty() ? categoryElement.getSimpleName().toString() : category.id(); - if (categoryId.equals(action.categoryId())) { - Pair actionResult = this.processAction(roundEnv, pluginElement, plugin, categoryElement, category, actionElement); - jsonActions.add(actionResult.first); - actionsTypeSpecBuilder.addType(actionResult.second.build()); - } - } - categoryTypeSpecBuilder.addType(actionsTypeSpecBuilder.build()); - jsonCategory.add(CategoryHelper.ACTIONS, jsonActions); - - TypeSpec.Builder connectorsTypeSpecBuilder = TypeSpec.classBuilder("Connectors").addModifiers(Modifier.PUBLIC, Modifier.STATIC); - JsonArray jsonConnectors = new JsonArray(); - Set connectorElements = roundEnv.getElementsAnnotatedWith(Connector.class); - for (Element connectorElement : connectorElements) { - Connector connector = connectorElement.getAnnotation(Connector.class); - String categoryId = category.id().isEmpty() ? categoryElement.getSimpleName().toString() : category.id(); - if (categoryId.equals(connector.categoryId())) { - Pair connectorResult = this.processConnector(roundEnv, pluginElement, plugin, categoryElement, category, connectorElement); - jsonConnectors.add(connectorResult.first); - connectorsTypeSpecBuilder.addType(connectorResult.second.build()); - } - } - categoryTypeSpecBuilder.addType(connectorsTypeSpecBuilder.build()); - jsonCategory.add(CategoryHelper.CONNECTORS, jsonConnectors); - - TypeSpec.Builder statesTypeSpecBuilder = TypeSpec.classBuilder("States").addModifiers(Modifier.PUBLIC, Modifier.STATIC); - JsonArray jsonStates = new JsonArray(); - Set stateElements = roundEnv.getElementsAnnotatedWith(State.class); - for (Element stateElement : stateElements) { - State state = stateElement.getAnnotation(State.class); - String categoryId = category.id().isEmpty() ? categoryElement.getSimpleName().toString() : category.id(); - if (categoryId.equals(state.categoryId())) { - Pair stateResult = this.processState(roundEnv, pluginElement, plugin, categoryElement, category, stateElement); - jsonStates.add(stateResult.first); - statesTypeSpecBuilder.addType(stateResult.second.build()); - } - } - categoryTypeSpecBuilder.addType(statesTypeSpecBuilder.build()); - jsonCategory.add(CategoryHelper.STATES, jsonStates); - - TypeSpec.Builder eventsTypeSpecBuilder = TypeSpec.classBuilder("Events").addModifiers(Modifier.PUBLIC, Modifier.STATIC); - JsonArray jsonEvents = new JsonArray(); - Set eventElements = roundEnv.getElementsAnnotatedWith(Event.class); - for (Element eventElement : eventElements) { - State state = eventElement.getAnnotation(State.class); - String categoryId = category.id().isEmpty() ? categoryElement.getSimpleName().toString() : category.id(); - if (state != null) { - if (categoryId.equals(state.categoryId())) { - Pair eventResult = this.processEvent(roundEnv, pluginElement, plugin, categoryElement, category, eventElement); - jsonEvents.add(eventResult.first); - eventsTypeSpecBuilder.addType(eventResult.second.build()); - } - } - else { - throw new AnnotationFormatError("The State Annotation is missing for element " + eventElement.getSimpleName()); - } - } - categoryTypeSpecBuilder.addType(eventsTypeSpecBuilder.build()); - jsonCategory.add(CategoryHelper.EVENTS, jsonEvents); - - return Pair.create(jsonCategory, categoryTypeSpecBuilder); - } - - /** - * Generates a JsonObject and a TypeSpec.Builder representing the {@link Action} - * - * @param roundEnv RoundEnvironment - * @param pluginElement Element - * @param plugin {@link Plugin} - * @param categoryElement Element - * @param category {@link Category} - * @param actionElement Element - * @return Pair actionPair - */ - private Pair processAction(RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element actionElement) throws Exception { - this.messager.printMessage(Diagnostic.Kind.NOTE, "Process Action: " + actionElement.getSimpleName()); - Action action = actionElement.getAnnotation(Action.class); - - TypeSpec.Builder actionTypeSpecBuilder = this.createActionTypeSpecBuilder(pluginElement, categoryElement, category, actionElement, action); - - JsonObject jsonAction = new JsonObject(); - jsonAction.addProperty(ActionHelper.ID, ActionHelper.getActionId(pluginElement, categoryElement, category, actionElement, action)); - jsonAction.addProperty(ActionHelper.NAME, ActionHelper.getActionName(actionElement, action)); - jsonAction.addProperty(ActionHelper.PREFIX, action.prefix()); - jsonAction.addProperty(ActionHelper.TYPE, action.type()); - if (!action.description().isEmpty()) { - jsonAction.addProperty(ActionHelper.DESCRIPTION, action.description()); - } - if (!action.format().isEmpty()) { - jsonAction.addProperty(ActionHelper.FORMAT, action.format()); - jsonAction.addProperty(ActionHelper.TRY_INLINE, true); - } - jsonAction.addProperty(ActionHelper.HAS_HOLD_FUNCTIONALITY, action.hasHoldFunctionality()); - - JsonArray jsonActionData = new JsonArray(); - Set dataElements = roundEnv.getElementsAnnotatedWith(Data.class); - for (Element dataElement : dataElements) { - Element enclosingElement = dataElement.getEnclosingElement(); - if (actionElement.equals(enclosingElement)) { - Pair actionDataResult = this.processActionData(roundEnv, pluginElement, plugin, categoryElement, category, actionElement, action, jsonAction, dataElement); - jsonActionData.add(actionDataResult.first); - if (actionDataResult.second != null) { - actionTypeSpecBuilder.addType(actionDataResult.second.build()); - } - } - } - if (jsonActionData.size() > 0) { - jsonAction.add(ActionHelper.DATA, jsonActionData); - } - - return Pair.create(jsonAction, actionTypeSpecBuilder); - } - - /** - * Generates a JsonObject and a TypeSpec.Builder representing the {@link Connector} - * - * @param roundEnv RoundEnvironment - * @param pluginElement Element - * @param plugin {@link Plugin} - * @param categoryElement Element - * @param category {@link Category} - * @param connectorElement Element - * @return Pair actionPair - */ - private Pair processConnector(RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element connectorElement) throws Exception { - this.messager.printMessage(Diagnostic.Kind.NOTE, "Process Connector: " + connectorElement.getSimpleName()); - Connector connector = connectorElement.getAnnotation(Connector.class); - - TypeSpec.Builder connectorTypeSpecBuilder = this.createConnectorTypeSpecBuilder(pluginElement, categoryElement, category, connectorElement, connector); - - JsonObject jsonConnector = new JsonObject(); - jsonConnector.addProperty(ConnectorHelper.ID, ConnectorHelper.getConnectorId(pluginElement, categoryElement, category, connectorElement, connector)); - jsonConnector.addProperty(ConnectorHelper.NAME, ConnectorHelper.getConnectorName(connectorElement, connector)); - jsonConnector.addProperty(ConnectorHelper.FORMAT, connector.format()); - - String classMethod = pluginElement.getSimpleName() + "." + connectorElement.getSimpleName(); - boolean connectorValueFound = false; - Set connectorValueElements = roundEnv.getElementsAnnotatedWith(ConnectorValue.class); - for (Element connectorValueElement : connectorValueElements) { - Element enclosingElement = connectorValueElement.getEnclosingElement(); - if (connectorElement.equals(enclosingElement)) { - String classMethodParameter = classMethod + "(" + connectorValueElement.getSimpleName() + ")"; - String desiredType = GenericHelper.getTouchPortalType(classMethodParameter, connectorValueElement); - if (!desiredType.equals(GenericHelper.TP_TYPE_NUMBER)) { - throw new GenericHelper.TPTypeException.Builder(classMethodParameter).typeUnsupported(desiredType).forAnnotation(GenericHelper.TPTypeException.ForAnnotation.CONNECTOR_VALUE).build(); - } - connectorValueFound = true; - break; - } - } - if (!connectorValueFound) { - throw new IllegalArgumentException("Connector " + classMethod + " has no declared ConnectorValue"); - } - - JsonArray jsonConnectorData = new JsonArray(); - Set dataElements = roundEnv.getElementsAnnotatedWith(Data.class); - for (Element dataElement : dataElements) { - Element enclosingElement = dataElement.getEnclosingElement(); - if (connectorElement.equals(enclosingElement)) { - Pair connectorDataResult = this.processConnectorData(roundEnv, pluginElement, plugin, categoryElement, category, connectorElement, connector, jsonConnector, dataElement); - jsonConnectorData.add(connectorDataResult.first); - if (connectorDataResult.second != null) { - connectorTypeSpecBuilder.addType(connectorDataResult.second.build()); - } - } - } - if (jsonConnectorData.size() > 0) { - jsonConnector.add(ConnectorHelper.DATA, jsonConnectorData); - } - - return Pair.create(jsonConnector, connectorTypeSpecBuilder); - } - - /** - * Generates a JsonObject and a TypeSpec.Builder representing the {@link State} - * - * @param roundEnv RoundEnvironment - * @param pluginElement Element - * @param plugin {@link Plugin} - * @param categoryElement Element - * @param category {@link Category} - * @param stateElement Element - * @return Pair statePair - * @throws GenericHelper.TPTypeException If a used type is not Supported - */ - private Pair processState(RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element stateElement) throws Exception { - this.messager.printMessage(Diagnostic.Kind.NOTE, "Process State: " + stateElement.getSimpleName()); - State state = stateElement.getAnnotation(State.class); - - TypeSpec.Builder stateTypeSpecBuilder = this.createStateTypeSpecBuilder(pluginElement, categoryElement, category, stateElement, state); - - String className = stateElement.getEnclosingElement().getSimpleName() + "." + stateElement.getSimpleName(); - - JsonObject jsonState = new JsonObject(); - jsonState.addProperty(StateHelper.ID, StateHelper.getStateId(pluginElement, categoryElement, category, stateElement, state)); - String desiredTPType = GenericHelper.getTouchPortalType(className, stateElement); - jsonState.addProperty(StateHelper.TYPE, desiredTPType); - jsonState.addProperty(StateHelper.DESC, StateHelper.getStateDesc(stateElement, state)); - jsonState.addProperty(StateHelper.DEFAULT, state.defaultValue()); - if (desiredTPType.equals(StateHelper.TYPE_CHOICE)) { - JsonArray stateValueChoices = new JsonArray(); - for (String valueChoice : state.valueChoices()) { - stateValueChoices.add(valueChoice); - } - jsonState.add(StateHelper.VALUE_CHOICES, stateValueChoices); - } - else if (!desiredTPType.equals(StateHelper.TYPE_TEXT)) { - throw new GenericHelper.TPTypeException.Builder(className).typeUnsupported(desiredTPType).forAnnotation(GenericHelper.TPTypeException.ForAnnotation.STATE).build(); - } - - Event event = stateElement.getAnnotation(Event.class); - if (event != null && !desiredTPType.equals(StateHelper.TYPE_TEXT)) { - throw new Exception("The type of the State Annotation for " + className + " cannot be " + desiredTPType + " because the field is also Annotated with Event. Only the type " + StateHelper.TYPE_TEXT + " is supported for a State that has an Event Annotation."); - } - - return Pair.create(jsonState, stateTypeSpecBuilder); - } - - /** - * Generates a JsonObject and a TypeSpec.Builder representing the {@link Event} - * - * @param roundEnv RoundEnvironment - * @param pluginElement Element - * @param plugin {@link Plugin} - * @param categoryElement Element - * @param category {@link Category} - * @param eventElement Element - * @return Pair eventPair - * @throws GenericHelper.TPTypeException If any used type is not Supported - */ - private Pair processEvent(RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element eventElement) throws Exception { - this.messager.printMessage(Diagnostic.Kind.NOTE, "Process Event: " + eventElement.getSimpleName()); - State state = eventElement.getAnnotation(State.class); - Event event = eventElement.getAnnotation(Event.class); - - String reference = eventElement.getEnclosingElement().getSimpleName() + "." + eventElement.getSimpleName(); - - if (state == null) { - throw new Exception("The Event Annotation on " + reference + " must be used with the State Annotation"); - } - - TypeSpec.Builder eventTypeSpecBuilder = this.createEventTypeSpecBuilder(pluginElement, categoryElement, category, eventElement, event); - - JsonObject jsonEvent = new JsonObject(); - jsonEvent.addProperty(EventHelper.ID, EventHelper.getEventId(pluginElement, categoryElement, category, eventElement, event)); - jsonEvent.addProperty(EventHelper.TYPE, EventHelper.TYPE_COMMUNICATE); - jsonEvent.addProperty(EventHelper.NAME, EventHelper.getEventName(eventElement, event)); - jsonEvent.addProperty(EventHelper.FORMAT, event.format()); - String desiredTPType = GenericHelper.getTouchPortalType(reference, eventElement); - if (desiredTPType.equals(StateHelper.TYPE_TEXT)) { - jsonEvent.addProperty(EventHelper.VALUE_TYPE, EventHelper.VALUE_TYPE_CHOICE); - JsonArray eventValueChoices = new JsonArray(); - for (String valueChoice : event.valueChoices()) { - eventValueChoices.add(valueChoice); - } - jsonEvent.add(EventHelper.VALUE_CHOICES, eventValueChoices); - jsonEvent.addProperty(EventHelper.VALUE_STATE_ID, StateHelper.getStateId(pluginElement, categoryElement, category, eventElement, state)); - } - else { - throw new GenericHelper.TPTypeException.Builder(reference).typeUnsupported(desiredTPType).forAnnotation(GenericHelper.TPTypeException.ForAnnotation.EVENT).build(); - } - - return Pair.create(jsonEvent, eventTypeSpecBuilder); - } - - /** - * Generates a JsonObject and a TypeSpec.Builder representing the {@link Data} for an {@link Action} - * - * @param roundEnv RoundEnvironment - * @param pluginElement Element - * @param plugin {@link Plugin} - * @param categoryElement Element - * @param category {@link Category} - * @param actionElement Element - * @param action {@link Action} - * @param jsonAction JsonObject - * @param dataElement Element - * @return Pair dataPair - */ - private Pair processActionData(RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element actionElement, Action action, JsonObject jsonAction, Element dataElement) throws Exception { - this.messager.printMessage(Diagnostic.Kind.NOTE, "Process Action Data: " + dataElement.getSimpleName()); - Data data = dataElement.getAnnotation(Data.class); - - TypeSpec.Builder actionDataTypeSpecBuilder = this.createActionDataTypeSpecBuilder(pluginElement, categoryElement, category, actionElement, action, dataElement, data); - - Element method = dataElement.getEnclosingElement(); - String className = method.getEnclosingElement().getSimpleName() + "." + method.getSimpleName() + "(" + dataElement.getSimpleName() + ")"; - - JsonObject jsonData = new JsonObject(); - String desiredTPType = GenericHelper.getTouchPortalType(className, dataElement); - jsonData.addProperty(DataHelper.TYPE, desiredTPType); - jsonData.addProperty(DataHelper.LABEL, DataHelper.getDataLabel(dataElement, data)); - // Default Value - switch (desiredTPType) { - case GenericHelper.TP_TYPE_NUMBER: - double defaultValue = 0; - try { - defaultValue = Double.parseDouble(data.defaultValue()); - } - catch (NumberFormatException ignored) {} - jsonData.addProperty(DataHelper.DEFAULT, defaultValue); - break; - - case GenericHelper.TP_TYPE_SWITCH: - jsonData.addProperty(DataHelper.DEFAULT, data.defaultValue().equals("true")); - break; - - default: - jsonData.addProperty(DataHelper.DEFAULT, data.defaultValue()); - break; - } - AtomicReference dataId = new AtomicReference<>(DataHelper.getActionDataId(pluginElement, categoryElement, category, actionElement, action, dataElement, data)); - // Specific properties - switch (desiredTPType) { - case GenericHelper.TP_TYPE_CHOICE: - JsonArray dataValueChoices = new JsonArray(); - if (!data.stateId().isEmpty()) { - Optional optionalStateElement = roundEnv.getElementsAnnotatedWith(State.class).stream().filter(element -> { - State state = element.getAnnotation(State.class); - String shortStateId = !state.id().isEmpty() ? state.id() : element.getSimpleName().toString(); - return shortStateId.equals(data.stateId()); - }).findFirst(); - if (optionalStateElement.isPresent()) { - actionDataTypeSpecBuilder = null; - - Element stateElement = optionalStateElement.get(); - State state = stateElement.getAnnotation(State.class); - dataId.set(StateHelper.getStateId(pluginElement, categoryElement, category, stateElement, state)); - for (String valueChoice : state.valueChoices()) { - dataValueChoices.add(valueChoice); - } - jsonData.addProperty(DataHelper.DEFAULT, data.defaultValue().isEmpty() ? state.defaultValue() : data.defaultValue()); - } - else { - for (String valueChoice : data.valueChoices()) { - dataValueChoices.add(valueChoice); - } - } - } - else { - for (String valueChoice : data.valueChoices()) { - dataValueChoices.add(valueChoice); - } - } - jsonData.add(DataHelper.VALUE_CHOICES, dataValueChoices); - break; - - case GenericHelper.TP_TYPE_FILE: - if (data.isDirectory()) { - jsonData.addProperty(DataHelper.TYPE, GenericHelper.TP_TYPE_DIRECTORY); - } - else { - JsonArray jsonExtensions = new JsonArray(); - for (String extension : data.extensions()) { - if (extension.matches(DataHelper.EXTENSION_FORMAT)) { - jsonExtensions.add(extension); - } - else { - this.messager.printMessage(Diagnostic.Kind.ERROR, "Action Data Extension: [" + extension + "] format is not valid"); - } - } - jsonData.add(DataHelper.EXTENSIONS, jsonExtensions); - } - break; - - case GenericHelper.TP_TYPE_TEXT: - if (data.isColor()) { - jsonData.addProperty(DataHelper.TYPE, GenericHelper.TP_TYPE_COLOR); - if (!data.defaultValue().isEmpty()) { - if (!data.defaultValue().matches(DataHelper.COLOR_FORMAT)) { - this.messager.printMessage(Diagnostic.Kind.ERROR, "Action Data Color Default value: [" + data.defaultValue() + "] format is not valid"); - } - } - } - break; - - case GenericHelper.TP_TYPE_NUMBER: - try { - double defaultValue = jsonData.get(DataHelper.DEFAULT).getAsDouble(); - if (defaultValue < data.minValue() || defaultValue > data.maxValue()) { - throw new GenericHelper.TPTypeException.Builder(className).defaultNotInRange().build(); - } - } - catch (NumberFormatException numberFormatException) { - throw new GenericHelper.TPTypeException.Builder(className).defaultInvalid(data.defaultValue()).build(); - } - jsonData.addProperty(DataHelper.ALLOW_DECIMALS, GenericHelper.getTouchPortalTypeNumberAllowDecimals(dataElement.asType().toString())); - if (data.minValue() > Double.NEGATIVE_INFINITY) { - jsonData.addProperty(DataHelper.MIN_VALUE, data.minValue()); - } - if (data.maxValue() < Double.POSITIVE_INFINITY) { - jsonData.addProperty(DataHelper.MAX_VALUE, data.maxValue()); - } - break; - } - jsonData.addProperty(DataHelper.ID, dataId.get()); - if (!action.format().isEmpty()) { - // Replace wildcards - String rawFormat = jsonAction.get(ActionHelper.FORMAT).getAsString(); - jsonAction.addProperty(ActionHelper.FORMAT, rawFormat.replace("{$" + (data.id().isEmpty() ? dataElement.getSimpleName().toString() : data.id()) + "$}", "{$" + dataId.get() + "$}")); - } - - return Pair.create(jsonData, actionDataTypeSpecBuilder); - } - - /** - * Generates a JsonObject and a TypeSpec.Builder representing the {@link Data} for a {@link Connector} - * - * @param roundEnv RoundEnvironment - * @param pluginElement Element - * @param plugin {@link Plugin} - * @param categoryElement Element - * @param category {@link Category} - * @param connectorElement Element - * @param connector {@link Connector} - * @param jsonConnector JsonObject - * @param dataElement Element - * @return Pair dataPair - */ - private Pair processConnectorData(RoundEnvironment roundEnv, Element pluginElement, Plugin plugin, Element categoryElement, Category category, Element connectorElement, Connector connector, JsonObject jsonConnector, Element dataElement) throws Exception { - this.messager.printMessage(Diagnostic.Kind.NOTE, "Process Connector Data: " + dataElement.getSimpleName()); - Data data = dataElement.getAnnotation(Data.class); - - TypeSpec.Builder connectorDataTypeSpecBuilder = this.createConnectorDataTypeSpecBuilder(pluginElement, categoryElement, category, connectorElement, connector, dataElement, data); - - Element method = dataElement.getEnclosingElement(); - String className = method.getEnclosingElement().getSimpleName() + "." + method.getSimpleName() + "(" + dataElement.getSimpleName() + ")"; - - JsonObject jsonData = new JsonObject(); - String desiredTPType = GenericHelper.getTouchPortalType(className, dataElement); - jsonData.addProperty(DataHelper.TYPE, desiredTPType); - jsonData.addProperty(DataHelper.LABEL, DataHelper.getDataLabel(dataElement, data)); - // Default Value - switch (desiredTPType) { - case GenericHelper.TP_TYPE_NUMBER: - double defaultValue = 0; - try { - defaultValue = Double.parseDouble(data.defaultValue()); - } - catch (NumberFormatException ignored) {} - jsonData.addProperty(DataHelper.DEFAULT, defaultValue); - break; - - case GenericHelper.TP_TYPE_SWITCH: - jsonData.addProperty(DataHelper.DEFAULT, data.defaultValue().equals("true")); - break; - - default: - jsonData.addProperty(DataHelper.DEFAULT, data.defaultValue()); - break; - } - AtomicReference dataId = new AtomicReference<>(DataHelper.getConnectorDataId(pluginElement, categoryElement, category, connectorElement, connector, dataElement, data)); - // Specific properties - switch (desiredTPType) { - case GenericHelper.TP_TYPE_CHOICE: - JsonArray dataValueChoices = new JsonArray(); - if (!data.stateId().isEmpty()) { - Optional optionalStateElement = roundEnv.getElementsAnnotatedWith(State.class).stream().filter(element -> { - State state = element.getAnnotation(State.class); - String shortStateId = !state.id().isEmpty() ? state.id() : element.getSimpleName().toString(); - return shortStateId.equals(data.stateId()); - }).findFirst(); - if (optionalStateElement.isPresent()) { - connectorDataTypeSpecBuilder = null; - - Element stateElement = optionalStateElement.get(); - State state = stateElement.getAnnotation(State.class); - dataId.set(StateHelper.getStateId(pluginElement, categoryElement, category, stateElement, state)); - for (String valueChoice : state.valueChoices()) { - dataValueChoices.add(valueChoice); - } - jsonData.addProperty(DataHelper.DEFAULT, data.defaultValue().isEmpty() ? state.defaultValue() : data.defaultValue()); - } - else { - for (String valueChoice : data.valueChoices()) { - dataValueChoices.add(valueChoice); - } - } - } - else { - for (String valueChoice : data.valueChoices()) { - dataValueChoices.add(valueChoice); - } - } - jsonData.add(DataHelper.VALUE_CHOICES, dataValueChoices); - break; - - case GenericHelper.TP_TYPE_FILE: - if (data.isDirectory()) { - jsonData.addProperty(DataHelper.TYPE, GenericHelper.TP_TYPE_DIRECTORY); - } - else { - JsonArray jsonExtensions = new JsonArray(); - for (String extension : data.extensions()) { - if (extension.matches(DataHelper.EXTENSION_FORMAT)) { - jsonExtensions.add(extension); - } - else { - this.messager.printMessage(Diagnostic.Kind.ERROR, "Action Data Extension: [" + extension + "] format is not valid"); - } - } - jsonData.add(DataHelper.EXTENSIONS, jsonExtensions); - } - break; - - case GenericHelper.TP_TYPE_TEXT: - if (data.isColor()) { - jsonData.addProperty(DataHelper.TYPE, GenericHelper.TP_TYPE_COLOR); - if (!data.defaultValue().isEmpty()) { - if (!data.defaultValue().matches(DataHelper.COLOR_FORMAT)) { - this.messager.printMessage(Diagnostic.Kind.ERROR, "Action Data Color Default value: [" + data.defaultValue() + "] format is not valid"); - } - } - } - break; - - case GenericHelper.TP_TYPE_NUMBER: - try { - double defaultValue = jsonData.get(DataHelper.DEFAULT).getAsDouble(); - if (defaultValue < data.minValue() || defaultValue > data.maxValue()) { - throw new GenericHelper.TPTypeException.Builder(className).defaultNotInRange().build(); - } - } - catch (NumberFormatException numberFormatException) { - throw new GenericHelper.TPTypeException.Builder(className).defaultInvalid(data.defaultValue()).build(); - } - jsonData.addProperty(DataHelper.ALLOW_DECIMALS, GenericHelper.getTouchPortalTypeNumberAllowDecimals(dataElement.asType().toString())); - if (data.minValue() > Double.NEGATIVE_INFINITY) { - jsonData.addProperty(DataHelper.MIN_VALUE, data.minValue()); - } - if (data.maxValue() < Double.POSITIVE_INFINITY) { - jsonData.addProperty(DataHelper.MAX_VALUE, data.maxValue()); - } - break; - } - jsonData.addProperty(DataHelper.ID, dataId.get()); - if (!connector.format().isEmpty()) { - // Replace wildcards - String rawFormat = jsonConnector.get(ConnectorHelper.FORMAT).getAsString(); - jsonConnector.addProperty(ConnectorHelper.FORMAT, rawFormat.replace("{$" + (data.id().isEmpty() ? dataElement.getSimpleName().toString() : data.id()) + "$}", "{$" + dataId.get() + "$}")); - } - - return Pair.create(jsonData, connectorDataTypeSpecBuilder); - } - - /** - * Generates a TypeSpec.Builder with Constants for the {@link Plugin} - * - * @param pluginElement Element - * @param plugin {@link Plugin} - * @return TypeSpec.Builder pluginTypeSpecBuilder - */ - private TypeSpec.Builder createPluginTypeSpecBuilder(Element pluginElement, Plugin plugin) { - String simpleClassName = pluginElement.getSimpleName().toString() + "Constants"; - - TypeSpec.Builder pluginTypeSpecBuilder = TypeSpec.classBuilder(TouchPortalPluginAnnotationProcessor.capitalize(simpleClassName)); - pluginTypeSpecBuilder.addModifiers(Modifier.PUBLIC); - - pluginTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("id", PluginHelper.getPluginId(pluginElement))); - pluginTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("name", plugin.name())); - pluginTypeSpecBuilder.addField(this.getStaticFinalLongFieldSpec("version", plugin.version())); - - return pluginTypeSpecBuilder; - } - - /** - * Generates a TypeSpec.Builder with Constants for the {@link Category} - * - * @param pluginElement Element - * @param categoryElement Element - * @param category {@link Category} - * @return TypeSpec.Builder pluginTypeSpecBuilder - */ - private TypeSpec.Builder createCategoryTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category) { - String simpleClassName = category.id().isEmpty() ? categoryElement.getSimpleName().toString() : category.id(); - - TypeSpec.Builder categoryTypeSpecBuilder = TypeSpec.classBuilder(TouchPortalPluginAnnotationProcessor.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); - categoryTypeSpecBuilder.addModifiers(Modifier.PUBLIC); - - categoryTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("id", CategoryHelper.getCategoryId(pluginElement, categoryElement, category))); - categoryTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("name", category.name())); - categoryTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("image_path", category.imagePath())); - - return categoryTypeSpecBuilder; - } - - /** - * Generates a TypeSpec.Builder with Constants for the {@link Action} - * - * @param pluginElement Element - * @param categoryElement Element - * @param category {@link Category} - * @param actionElement Element - * @param action {@link Action} - * @return TypeSpec.Builder actionTypeSpecBuilder - */ - private TypeSpec.Builder createActionTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element actionElement, Action action) { - String simpleClassName = action.id().isEmpty() ? actionElement.getSimpleName().toString() : action.id(); - - TypeSpec.Builder actionTypeSpecBuilder = TypeSpec.classBuilder(TouchPortalPluginAnnotationProcessor.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); - actionTypeSpecBuilder.addModifiers(Modifier.PUBLIC); - - actionTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("id", ActionHelper.getActionId(pluginElement, categoryElement, category, actionElement, action))); - actionTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("name", ActionHelper.getActionName(actionElement, action))); - actionTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("prefix", action.prefix())); - actionTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("description", action.description())); - actionTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("type", action.type())); - actionTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("format", action.format())); - actionTypeSpecBuilder.addField(this.getStaticFinalBooleanFieldSpec("has_hold_functionality", action.hasHoldFunctionality())); - - return actionTypeSpecBuilder; - } - - /** - * Generates a TypeSpec.Builder with Constants for the {@link Action} - * - * @param pluginElement Element - * @param categoryElement Element - * @param category {@link Category} - * @param connectorElement Element - * @param connector {@link Action} - * @return TypeSpec.Builder actionTypeSpecBuilder - */ - private TypeSpec.Builder createConnectorTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element connectorElement, Connector connector) { - String simpleClassName = connector.id().isEmpty() ? connectorElement.getSimpleName().toString() : connector.id(); - - TypeSpec.Builder actionTypeSpecBuilder = TypeSpec.classBuilder(TouchPortalPluginAnnotationProcessor.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); - actionTypeSpecBuilder.addModifiers(Modifier.PUBLIC); - - actionTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("id", ConnectorHelper.getConnectorId(pluginElement, categoryElement, category, connectorElement, connector))); - actionTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("name", ConnectorHelper.getConnectorName(connectorElement, connector))); - actionTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("format", connector.format())); - - return actionTypeSpecBuilder; - } - - /** - * Generates a TypeSpec.Builder with Constants for the {@link Data} for an {@link Action} - * - * @param pluginElement Element - * @param categoryElement Element - * @param category {@link Category} - * @param actionElement Element - * @param action {@link Action} - * @param dataElement Element - * @param data {@link Data} - * @return TypeSpec.Builder dataTypeSpecBuilder - */ - private TypeSpec.Builder createActionDataTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element actionElement, Action action, Element dataElement, Data data) { - String simpleClassName = data.id().isEmpty() ? dataElement.getSimpleName().toString() : data.id(); - - TypeSpec.Builder actionDataTypeSpecBuilder = TypeSpec.classBuilder(TouchPortalPluginAnnotationProcessor.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); - actionDataTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("id", DataHelper.getActionDataId(pluginElement, categoryElement, category, actionElement, action, dataElement, data))); - actionDataTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("label", DataHelper.getDataLabel(dataElement, data))); - actionDataTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("default_value", data.defaultValue())); - actionDataTypeSpecBuilder.addField(this.getStaticFinalStringArrayFieldSpec("value_choices", data.valueChoices())); - actionDataTypeSpecBuilder.addField(this.getStaticFinalStringArrayFieldSpec("extensions", data.extensions())); - actionDataTypeSpecBuilder.addField(this.getStaticFinalBooleanFieldSpec("is_directory", data.isDirectory())); - actionDataTypeSpecBuilder.addField(this.getStaticFinalBooleanFieldSpec("is_color", data.isColor())); - if (data.minValue() > Double.NEGATIVE_INFINITY) { - actionDataTypeSpecBuilder.addField(this.getStaticFinalDoubleFieldSpec("min_value", data.minValue())); - } - if (data.maxValue() < Double.POSITIVE_INFINITY) { - actionDataTypeSpecBuilder.addField(this.getStaticFinalDoubleFieldSpec("max_value", data.maxValue())); - } - - return actionDataTypeSpecBuilder; - } - - /** - * Generates a TypeSpec.Builder with Constants for the {@link Data} for a {@link Connector} - * - * @param pluginElement Element - * @param categoryElement Element - * @param category {@link Category} - * @param connectorElement Element - * @param connector {@link Connector} - * @param dataElement Element - * @param data {@link Data} - * @return TypeSpec.Builder dataTypeSpecBuilder - */ - private TypeSpec.Builder createConnectorDataTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element connectorElement, Connector connector, Element dataElement, Data data) { - String simpleClassName = data.id().isEmpty() ? dataElement.getSimpleName().toString() : data.id(); - - TypeSpec.Builder connectorDataTypeSpecBuilder = TypeSpec.classBuilder(TouchPortalPluginAnnotationProcessor.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); - connectorDataTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("id", DataHelper.getConnectorDataId(pluginElement, categoryElement, category, connectorElement, connector, dataElement, data))); - connectorDataTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("label", DataHelper.getDataLabel(dataElement, data))); - connectorDataTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("default_value", data.defaultValue())); - connectorDataTypeSpecBuilder.addField(this.getStaticFinalStringArrayFieldSpec("value_choices", data.valueChoices())); - connectorDataTypeSpecBuilder.addField(this.getStaticFinalStringArrayFieldSpec("extensions", data.extensions())); - connectorDataTypeSpecBuilder.addField(this.getStaticFinalBooleanFieldSpec("is_directory", data.isDirectory())); - connectorDataTypeSpecBuilder.addField(this.getStaticFinalBooleanFieldSpec("is_color", data.isColor())); - if (data.minValue() > Double.NEGATIVE_INFINITY) { - connectorDataTypeSpecBuilder.addField(this.getStaticFinalDoubleFieldSpec("min_value", data.minValue())); - } - if (data.maxValue() < Double.POSITIVE_INFINITY) { - connectorDataTypeSpecBuilder.addField(this.getStaticFinalDoubleFieldSpec("max_value", data.maxValue())); - } - - return connectorDataTypeSpecBuilder; - } - - /** - * Generates a TypeSpec.Builder with Constants for the {@link Setting} - * - * @param settingElement Element - * @param setting {@link Setting} - * @return TypeSpec.Builder stateTypeSpecBuilder - */ - private TypeSpec.Builder createSettingTypeSpecBuilder(Element settingElement, Setting setting) { - String simpleClassName = settingElement.getSimpleName().toString(); - - TypeSpec.Builder stateTypeSpecBuilder = TypeSpec.classBuilder(TouchPortalPluginAnnotationProcessor.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); - stateTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("name", SettingHelper.getSettingName(settingElement, setting))); - stateTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("default", setting.defaultValue())); - stateTypeSpecBuilder.addField(this.getStaticFinalDoubleFieldSpec("max_length", setting.maxLength())); - stateTypeSpecBuilder.addField(this.getStaticFinalBooleanFieldSpec("is_password", setting.isPassword())); - if (setting.minValue() > Double.NEGATIVE_INFINITY) { - stateTypeSpecBuilder.addField(this.getStaticFinalDoubleFieldSpec("min_value", setting.minValue())); - } - if (setting.maxValue() < Double.POSITIVE_INFINITY) { - stateTypeSpecBuilder.addField(this.getStaticFinalDoubleFieldSpec("max_value", setting.maxValue())); - } - - return stateTypeSpecBuilder; - } - - /** - * Generates a TypeSpec.Builder with Constants for the {@link State} - * - * @param pluginElement Element - * @param categoryElement Element - * @param category {@link Category} - * @param stateElement Element - * @param state {@link State} - * @return TypeSpec.Builder stateTypeSpecBuilder - */ - private TypeSpec.Builder createStateTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element stateElement, State state) { - String simpleClassName = state.id().isEmpty() ? stateElement.getSimpleName().toString() : state.id(); - - TypeSpec.Builder stateTypeSpecBuilder = TypeSpec.classBuilder(TouchPortalPluginAnnotationProcessor.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); - stateTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("id", StateHelper.getStateId(pluginElement, categoryElement, category, stateElement, state))); - stateTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("desc", StateHelper.getStateDesc(stateElement, state))); - stateTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("default_value", state.defaultValue())); - stateTypeSpecBuilder.addField(this.getStaticFinalStringArrayFieldSpec("value_choices", state.valueChoices())); - - return stateTypeSpecBuilder; - } - - /** - * Generates a TypeSpec.Builder with Constants for the {@link Event} - * - * @param pluginElement Element - * @param categoryElement Element - * @param category {@link Category} - * @param eventElement Element - * @param event {@link Event} - * @return TypeSpec.Builder eventTypeSpecBuilder - */ - private TypeSpec.Builder createEventTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element eventElement, Event event) { - String simpleClassName = event.id().isEmpty() ? eventElement.getSimpleName().toString() : event.id(); - - TypeSpec.Builder eventTypeSpecBuilder = TypeSpec.classBuilder(TouchPortalPluginAnnotationProcessor.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); - eventTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("id", EventHelper.getEventId(pluginElement, categoryElement, category, eventElement, event))); - eventTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("name", EventHelper.getEventName(eventElement, event))); - eventTypeSpecBuilder.addField(this.getStaticFinalStringFieldSpec("format", event.format())); - eventTypeSpecBuilder.addField(this.getStaticFinalStringArrayFieldSpec("value_choices", event.valueChoices())); - - return eventTypeSpecBuilder; - } - - /** - * Internal Get a Static Final String Field initialised with value - * - * @param fieldName String - * @param value String - * @return FieldSpec fieldSpec - */ - private FieldSpec getStaticFinalStringFieldSpec(String fieldName, String value) { - return FieldSpec.builder(String.class, fieldName.toUpperCase()).addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("$S", value).build(); - } - - /** - * Internal Get a Static Final long Field initialised with value - * - * @param fieldName String - * @param value long - * @return FieldSpec fieldSpec - */ - private FieldSpec getStaticFinalDoubleFieldSpec(String fieldName, double value) { - return FieldSpec.builder(double.class, fieldName.toUpperCase()).addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("$L", value).build(); - } - - /** - * Internal Get a Static Final long Field initialised with value - * - * @param fieldName String - * @param value long - * @return FieldSpec fieldSpec - */ - private FieldSpec getStaticFinalLongFieldSpec(String fieldName, long value) { - return FieldSpec.builder(long.class, fieldName.toUpperCase()).addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("$L", value).build(); - } - - /** - * Internal Get a Static Final boolean Field initialised with value - * - * @param fieldName String - * @param value boolean - * @return FieldSpec fieldSpec - */ - private FieldSpec getStaticFinalBooleanFieldSpec(String fieldName, boolean value) { - return FieldSpec.builder(boolean.class, fieldName.toUpperCase()).addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("$L", value).build(); - } - - /** - * Internal Get a Static Final boolean Field initialised with value - * - * @param fieldName String - * @param values String[] - * @return FieldSpec fieldSpec - */ - private FieldSpec getStaticFinalStringArrayFieldSpec(String fieldName, String[] values) { - ArrayTypeName stringArray = ArrayTypeName.of(String.class); - String literal = "{\"" + String.join("\",\"", values) + "\"}"; - return FieldSpec.builder(String[].class, fieldName.toUpperCase()).addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("new $1T $2L", stringArray, literal).build(); - } -} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/TouchPortalPluginAnnotationsProcessor.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/TouchPortalPluginAnnotationsProcessor.java new file mode 100644 index 00000000..79bcd45e --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/TouchPortalPluginAnnotationsProcessor.java @@ -0,0 +1,121 @@ +/* + * Touch Portal Plugin SDK + * + * Copyright 2020 Christophe Carvalho Vilas-Boas + * christophe.carvalhovilasboas@gmail.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.christophecvb.touchportal.annotations.processor; + +import com.christophecvb.touchportal.annotations.*; +import com.christophecvb.touchportal.annotations.processor.utils.Pair; +import com.christophecvb.touchportal.annotations.processor.utils.SpecUtils; +import com.christophecvb.touchportal.helpers.*; +import com.google.auto.service.AutoService; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeSpec; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import java.io.Writer; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Touch Portal Plugin Annotations Processor + */ +@AutoService(Processor.class) +public class TouchPortalPluginAnnotationsProcessor extends AbstractProcessor { + private Filer filer; + private Messager messager; + + public Messager getMessager() { + return this.messager; + } + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + this.filer = processingEnv.getFiler(); + this.messager = processingEnv.getMessager(); + } + + @Override + public Set getSupportedAnnotationTypes() { + Set annotations = new LinkedHashSet<>(); + annotations.add(Plugin.class.getCanonicalName()); + annotations.add(Setting.class.getCanonicalName()); + annotations.add(Category.class.getCanonicalName()); + annotations.add(Action.class.getCanonicalName()); + annotations.add(ActionTranslation.class.getCanonicalName()); + annotations.add(ActionTranslations.class.getCanonicalName()); + annotations.add(Data.class.getCanonicalName()); + annotations.add(State.class.getCanonicalName()); + annotations.add(Event.class.getCanonicalName()); + annotations.add(Connector.class.getCanonicalName()); + return annotations; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver() || annotations.size() == 0) { + return false; + } + this.messager.printMessage(Diagnostic.Kind.NOTE, this.getClass().getSimpleName() + ".process"); + + try { + Set plugins = roundEnv.getElementsAnnotatedWith(Plugin.class); + if (plugins.size() != 1) { + throw new TPAnnotationException.Builder(Plugin.class).count(plugins.size()).build(); + } + for (Element pluginElement : plugins) { + Pair pluginPair = PluginProcessor.process(this, roundEnv, pluginElement); + + String entryFileName = "resources/" + PluginHelper.ENTRY_TP; + FileObject actionFileObject = this.filer.createResource(StandardLocation.SOURCE_OUTPUT, "", entryFileName, pluginElement); + Writer writer = actionFileObject.openWriter(); + writer.write(pluginPair.first.toString()); + writer.flush(); + writer.close(); + + TypeSpec pluginTypeSpec = pluginPair.second.build(); + String packageName = ((PackageElement) pluginElement.getEnclosingElement()).getQualifiedName().toString(); + JavaFile javaConstantsFile = JavaFile.builder(packageName, pluginTypeSpec).build(); + javaConstantsFile.writeTo(this.filer); + } + } + catch (Exception exception) { + this.messager.printMessage(Diagnostic.Kind.ERROR, exception.getMessage()); + } + + return true; + } +} diff --git a/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/utils/SpecUtils.java b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/utils/SpecUtils.java new file mode 100644 index 00000000..044022ff --- /dev/null +++ b/AnnotationsProcessor/src/main/java/com/christophecvb/touchportal/annotations/processor/utils/SpecUtils.java @@ -0,0 +1,325 @@ +package com.christophecvb.touchportal.annotations.processor.utils; + +import com.christophecvb.touchportal.annotations.*; +import com.christophecvb.touchportal.helpers.*; +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.TypeSpec; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; + +public class SpecUtils { + private static String capitalize(String str) { + if (str == null || str.isEmpty()) { + return str; + } + + return str.substring(0, 1).toUpperCase() + str.substring(1); + } + + /** + * Generates a TypeSpec.Builder with Constants for the {@link Plugin} + * + * @param pluginElement Element + * @param plugin {@link Plugin} + * @return TypeSpec.Builder pluginTypeSpecBuilder + */ + public static TypeSpec.Builder createPluginTypeSpecBuilder(Element pluginElement, Plugin plugin) { + String simpleClassName = pluginElement.getSimpleName().toString() + "Constants"; + + TypeSpec.Builder pluginTypeSpecBuilder = TypeSpec.classBuilder(SpecUtils.capitalize(simpleClassName)); + pluginTypeSpecBuilder.addModifiers(Modifier.PUBLIC); + + pluginTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("id", PluginHelper.getPluginId(pluginElement))); + pluginTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("name", plugin.name())); + pluginTypeSpecBuilder.addField(SpecUtils.getStaticFinalLongFieldSpec("version", plugin.version())); + + return pluginTypeSpecBuilder; + } + + /** + * Generates a TypeSpec.Builder with Constants for the {@link Category} + * + * @param pluginElement Element + * @param categoryElement Element + * @param category {@link Category} + * @return TypeSpec.Builder pluginTypeSpecBuilder + */ + public static TypeSpec.Builder createCategoryTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category) { + String simpleClassName = category.id().isEmpty() ? categoryElement.getSimpleName().toString() : category.id(); + + TypeSpec.Builder categoryTypeSpecBuilder = TypeSpec.classBuilder(SpecUtils.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); + categoryTypeSpecBuilder.addModifiers(Modifier.PUBLIC); + + categoryTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("id", CategoryHelper.getCategoryId(pluginElement, categoryElement, category))); + categoryTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("name", category.name())); + categoryTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("image_path", category.imagePath())); + + return categoryTypeSpecBuilder; + } + + /** + * Generates a TypeSpec.Builder with Constants for the {@link Action} + * + * @param pluginElement Element + * @param categoryElement Element + * @param category {@link Category} + * @param actionElement Element + * @param action {@link Action} + * @return TypeSpec.Builder actionTypeSpecBuilder + */ + public static TypeSpec.Builder createActionTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element actionElement, Action action) { + String simpleClassName = action.id().isEmpty() ? actionElement.getSimpleName().toString() : action.id(); + + TypeSpec.Builder actionTypeSpecBuilder = TypeSpec.classBuilder(SpecUtils.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); + actionTypeSpecBuilder.addModifiers(Modifier.PUBLIC); + + actionTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("id", ActionHelper.getActionId(pluginElement, categoryElement, category, actionElement, action))); + actionTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("name", ActionHelper.getActionName(actionElement, action))); + actionTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("prefix", action.prefix())); + actionTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("description", action.description())); + actionTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("type", action.type())); + actionTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("format", action.format())); + actionTypeSpecBuilder.addField(SpecUtils.getStaticFinalBooleanFieldSpec("has_hold_functionality", action.hasHoldFunctionality())); + + return actionTypeSpecBuilder; + } + + /** + * Generates a TypeSpec.Builder with Constants for the {@link Action} + * + * @param pluginElement Element + * @param categoryElement Element + * @param category {@link Category} + * @param connectorElement Element + * @param connector {@link Action} + * @return TypeSpec.Builder actionTypeSpecBuilder + */ + public static TypeSpec.Builder createConnectorTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element connectorElement, Connector connector) { + String simpleClassName = connector.id().isEmpty() ? connectorElement.getSimpleName().toString() : connector.id(); + + TypeSpec.Builder actionTypeSpecBuilder = TypeSpec.classBuilder(SpecUtils.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); + actionTypeSpecBuilder.addModifiers(Modifier.PUBLIC); + + actionTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("id", ConnectorHelper.getConnectorId(pluginElement, categoryElement, category, connectorElement, connector))); + actionTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("name", ConnectorHelper.getConnectorName(connectorElement, connector))); + actionTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("format", connector.format())); + + return actionTypeSpecBuilder; + } + + /** + * Generates a TypeSpec.Builder with Constants for the {@link Data} for an {@link Action} + * + * @param pluginElement Element + * @param categoryElement Element + * @param category {@link Category} + * @param actionElement Element + * @param action {@link Action} + * @param dataElement Element + * @param data {@link Data} + * @return TypeSpec.Builder dataTypeSpecBuilder + */ + public static TypeSpec.Builder createActionDataTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element actionElement, Action action, Element dataElement, Data data) { + String simpleClassName = data.id().isEmpty() ? dataElement.getSimpleName().toString() : data.id(); + + TypeSpec.Builder actionDataTypeSpecBuilder = TypeSpec.classBuilder(SpecUtils.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); + actionDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("id", DataHelper.getActionDataId(pluginElement, categoryElement, category, actionElement, action, dataElement, data))); + actionDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("label", DataHelper.getDataLabel(dataElement, data))); + actionDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("default_value", data.defaultValue())); + actionDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringArrayFieldSpec("value_choices", data.valueChoices())); + actionDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringArrayFieldSpec("extensions", data.extensions())); + actionDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalBooleanFieldSpec("is_directory", data.isDirectory())); + actionDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalBooleanFieldSpec("is_color", data.isColor())); + if (data.minValue() > Double.NEGATIVE_INFINITY) { + actionDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalDoubleFieldSpec("min_value", data.minValue())); + } + if (data.maxValue() < Double.POSITIVE_INFINITY) { + actionDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalDoubleFieldSpec("max_value", data.maxValue())); + } + + return actionDataTypeSpecBuilder; + } + + /** + * Generates a TypeSpec.Builder with Constants for the {@link Data} for a {@link Connector} + * + * @param pluginElement Element + * @param categoryElement Element + * @param category {@link Category} + * @param connectorElement Element + * @param connector {@link Connector} + * @param dataElement Element + * @param data {@link Data} + * @return TypeSpec.Builder dataTypeSpecBuilder + */ + public static TypeSpec.Builder createConnectorDataTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element connectorElement, Connector connector, Element dataElement, Data data) { + String simpleClassName = data.id().isEmpty() ? dataElement.getSimpleName().toString() : data.id(); + + TypeSpec.Builder connectorDataTypeSpecBuilder = TypeSpec.classBuilder(SpecUtils.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); + connectorDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("id", DataHelper.getConnectorDataId(pluginElement, categoryElement, category, connectorElement, connector, dataElement, data))); + connectorDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("label", DataHelper.getDataLabel(dataElement, data))); + connectorDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("default_value", data.defaultValue())); + connectorDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringArrayFieldSpec("value_choices", data.valueChoices())); + connectorDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringArrayFieldSpec("extensions", data.extensions())); + connectorDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalBooleanFieldSpec("is_directory", data.isDirectory())); + connectorDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalBooleanFieldSpec("is_color", data.isColor())); + if (data.minValue() > Double.NEGATIVE_INFINITY) { + connectorDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalDoubleFieldSpec("min_value", data.minValue())); + } + if (data.maxValue() < Double.POSITIVE_INFINITY) { + connectorDataTypeSpecBuilder.addField(SpecUtils.getStaticFinalDoubleFieldSpec("max_value", data.maxValue())); + } + + return connectorDataTypeSpecBuilder; + } + + /** + * Generates a TypeSpec.Builder with Constants for the {@link Setting} + * + * @param settingElement Element + * @param setting {@link Setting} + * @return TypeSpec.Builder stateTypeSpecBuilder + */ + public static TypeSpec.Builder createSettingTypeSpecBuilder(Element settingElement, Setting setting) { + String simpleClassName = settingElement.getSimpleName().toString(); + + TypeSpec.Builder stateTypeSpecBuilder = TypeSpec.classBuilder(SpecUtils.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); + stateTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("name", SettingHelper.getSettingName(settingElement, setting))); + stateTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("default", setting.defaultValue())); + stateTypeSpecBuilder.addField(SpecUtils.getStaticFinalDoubleFieldSpec("max_length", setting.maxLength())); + stateTypeSpecBuilder.addField(SpecUtils.getStaticFinalBooleanFieldSpec("is_password", setting.isPassword())); + if (setting.minValue() > Double.NEGATIVE_INFINITY) { + stateTypeSpecBuilder.addField(SpecUtils.getStaticFinalDoubleFieldSpec("min_value", setting.minValue())); + } + if (setting.maxValue() < Double.POSITIVE_INFINITY) { + stateTypeSpecBuilder.addField(SpecUtils.getStaticFinalDoubleFieldSpec("max_value", setting.maxValue())); + } + + if (!setting.tooltip().body().isEmpty()) { + stateTypeSpecBuilder.addType(createSettingTooltipTypeSpecBuilder(setting.tooltip()).build()); + } + + return stateTypeSpecBuilder; + } + + /** + * Generates a TypeSpec.Builder with Constants for the {@link Setting.Tooltip} + * + * @param tooltip {@link Setting.Tooltip} + * @return TypeSpec.Builder tooltipTypeSpecBuilder + */ + public static TypeSpec.Builder createSettingTooltipTypeSpecBuilder(Setting.Tooltip tooltip) { + TypeSpec.Builder tooltipTypeSpecBuilder = TypeSpec.classBuilder("Tooltip").addModifiers(Modifier.PUBLIC, Modifier.STATIC); + tooltipTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("body", tooltip.body())); + if (!tooltip.title().isEmpty()) { + tooltipTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("title", tooltip.title())); + } + if (!tooltip.title().isEmpty()) { + tooltipTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("docUrl", tooltip.docUrl())); + } + return tooltipTypeSpecBuilder; + } + + /** + * Generates a TypeSpec.Builder with Constants for the {@link State} + * + * @param pluginElement Element + * @param categoryElement Element + * @param category {@link Category} + * @param stateElement Element + * @param state {@link State} + * @return TypeSpec.Builder stateTypeSpecBuilder + */ + public static TypeSpec.Builder createStateTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element stateElement, State state) { + String simpleClassName = state.id().isEmpty() ? stateElement.getSimpleName().toString() : state.id(); + + TypeSpec.Builder stateTypeSpecBuilder = TypeSpec.classBuilder(SpecUtils.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); + stateTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("id", StateHelper.getStateId(pluginElement, categoryElement, category, stateElement, state))); + stateTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("desc", StateHelper.getStateDesc(stateElement, state))); + stateTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("default_value", state.defaultValue())); + stateTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringArrayFieldSpec("value_choices", state.valueChoices())); + + return stateTypeSpecBuilder; + } + + /** + * Generates a TypeSpec.Builder with Constants for the {@link Event} + * + * @param pluginElement Element + * @param categoryElement Element + * @param category {@link Category} + * @param eventElement Element + * @param event {@link Event} + * @return TypeSpec.Builder eventTypeSpecBuilder + */ + public static TypeSpec.Builder createEventTypeSpecBuilder(Element pluginElement, Element categoryElement, Category category, Element eventElement, Event event) { + String simpleClassName = event.id().isEmpty() ? eventElement.getSimpleName().toString() : event.id(); + + TypeSpec.Builder eventTypeSpecBuilder = TypeSpec.classBuilder(SpecUtils.capitalize(simpleClassName)).addModifiers(Modifier.PUBLIC, Modifier.STATIC); + eventTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("id", EventHelper.getEventId(pluginElement, categoryElement, category, eventElement, event))); + eventTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("name", EventHelper.getEventName(eventElement, event))); + eventTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringFieldSpec("format", event.format())); + eventTypeSpecBuilder.addField(SpecUtils.getStaticFinalStringArrayFieldSpec("value_choices", event.valueChoices())); + + return eventTypeSpecBuilder; + } + + /** + * Internal Get a Static Final String Field initialised with value + * + * @param fieldName String + * @param value String + * @return FieldSpec fieldSpec + */ + public static FieldSpec getStaticFinalStringFieldSpec(String fieldName, String value) { + return FieldSpec.builder(String.class, fieldName.toUpperCase()).addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("$S", value).build(); + } + + /** + * Internal Get a Static Final long Field initialised with value + * + * @param fieldName String + * @param value long + * @return FieldSpec fieldSpec + */ + public static FieldSpec getStaticFinalDoubleFieldSpec(String fieldName, double value) { + return FieldSpec.builder(double.class, fieldName.toUpperCase()).addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("$L", value).build(); + } + + /** + * Internal Get a Static Final long Field initialised with value + * + * @param fieldName String + * @param value long + * @return FieldSpec fieldSpec + */ + public static FieldSpec getStaticFinalLongFieldSpec(String fieldName, long value) { + return FieldSpec.builder(long.class, fieldName.toUpperCase()).addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("$L", value).build(); + } + + /** + * Internal Get a Static Final boolean Field initialised with value + * + * @param fieldName String + * @param value boolean + * @return FieldSpec fieldSpec + */ + public static FieldSpec getStaticFinalBooleanFieldSpec(String fieldName, boolean value) { + return FieldSpec.builder(boolean.class, fieldName.toUpperCase()).addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("$L", value).build(); + } + + /** + * Internal Get a Static Final boolean Field initialised with value + * + * @param fieldName String + * @param values String[] + * @return FieldSpec fieldSpec + */ + public static FieldSpec getStaticFinalStringArrayFieldSpec(String fieldName, String[] values) { + ArrayTypeName stringArray = ArrayTypeName.of(String.class); + String literal = "{\"" + String.join("\",\"", values) + "\"}"; + return FieldSpec.builder(String[].class, fieldName.toUpperCase()).addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("new $1T $2L", stringArray, literal).build(); + } +} diff --git a/AnnotationsProcessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/AnnotationsProcessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor index 85ca834c..ebd05a33 100644 --- a/AnnotationsProcessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/AnnotationsProcessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1 +1 @@ -com.christophecvb.touchportal.annotations.processor.TouchPortalPluginAnnotationProcessor +com.christophecvb.touchportal.annotations.processor.TouchPortalPluginAnnotationsProcessor diff --git a/Helpers/build.gradle b/Helpers/build.gradle index 5d24d203..b19d282c 100644 --- a/Helpers/build.gradle +++ b/Helpers/build.gradle @@ -77,6 +77,6 @@ javadoc { } dependencies { - api group: 'com.google.code.gson', name: 'gson', version: '2.9.0' + api libs.gson api project(':Annotations') } diff --git a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ActionHelper.java b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ActionHelper.java index 907ae911..4ded488e 100644 --- a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ActionHelper.java +++ b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ActionHelper.java @@ -24,6 +24,7 @@ import com.christophecvb.touchportal.annotations.Category; import javax.lang.model.element.Element; +import java.lang.reflect.Field; import java.lang.reflect.Method; /** @@ -109,6 +110,24 @@ public static String getActionId(Class pluginClass, Method actionMethod) { return actionId; } + /** + * Get the generated Action ID + * + * @param pluginClass Class + * @param actionField Field + * @return String actionId + */ + public static String getActionId(Class pluginClass, Field actionField) { + String actionId = ""; + + if (actionField.getDeclaringClass().isAnnotationPresent(Action.class)) { + Action action = actionField.getDeclaringClass().getDeclaredAnnotation(Action.class); + actionId = ActionHelper._getActionId(CategoryHelper.getCategoryId(pluginClass, action.categoryId()), (!action.id().isEmpty() ? action.id() : actionField.getDeclaringClass().getSimpleName())); + } + + return actionId; + } + /** * Internal - Get the formatted Action ID * diff --git a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ConnectorHelper.java b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ConnectorHelper.java index 6e0e20ca..993075f6 100644 --- a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ConnectorHelper.java +++ b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ConnectorHelper.java @@ -24,6 +24,7 @@ import com.christophecvb.touchportal.annotations.Connector; import javax.lang.model.element.Element; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; @@ -105,6 +106,24 @@ public static String getConnectorId(Class pluginClass, Method connectorMethod return connectorId; } + /** + * Get the generated Connector ID + * + * @param pluginClass Class + * @param connectorField Field + * @return String connectorId + */ + public static String getConnectorId(Class pluginClass, Field connectorField) { + String connectorId = ""; + + if (connectorField.getDeclaringClass().isAnnotationPresent(Connector.class)) { + Connector connector = connectorField.getDeclaringClass().getDeclaredAnnotation(Connector.class); + connectorId = ConnectorHelper.getConnectorId(pluginClass, connectorField.getDeclaringClass(), connector); + } + + return connectorId; + } + /** * Get the generated Connector ID * @@ -117,6 +136,18 @@ public static String getConnectorId(Class pluginClass, Method connectorMethod return ConnectorHelper._getConnectorId(CategoryHelper.getCategoryId(pluginClass, connector.categoryId()), !connector.id().isEmpty() ? connector.id() : connectorMethod.getName()); } + /** + * Get the generated Connector ID + * + * @param pluginClass Class + * @param connectorClass Class + * @param connector {@link Connector} + * @return String connectorId + */ + public static String getConnectorId(Class pluginClass, Class connectorClass, Connector connector) { + return ConnectorHelper._getConnectorId(CategoryHelper.getCategoryId(pluginClass, connector.categoryId()), !connector.id().isEmpty() ? connector.id() : connectorClass.getSimpleName()); + } + /** * Internal - Get the formatted Connector ID * diff --git a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/DataHelper.java b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/DataHelper.java index c9e0da87..6bce8c9d 100644 --- a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/DataHelper.java +++ b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/DataHelper.java @@ -23,6 +23,7 @@ import com.christophecvb.touchportal.annotations.*; import javax.lang.model.element.Element; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -150,6 +151,45 @@ else if (method.isAnnotationPresent(Connector.class)) { return dataId; } + /** + * Get the generated Data Id + * + * @param pluginClass Class + * @param field Field + * @return String dataId + */ + public static String getDataId(Class pluginClass, Field field) { + String dataId = ""; + + if (field.isAnnotationPresent(Data.class)) { + Data data = field.getAnnotation(Data.class); + if (!data.stateId().isEmpty()) { + String categoryId = null; + if (field.getDeclaringClass().isAnnotationPresent(Action.class)) { + Action action = field.getDeclaringClass().getAnnotation(Action.class); + categoryId = action.categoryId(); + } + else if (field.getDeclaringClass().isAnnotationPresent(Connector.class)) { + Connector connector = field.getDeclaringClass().getAnnotation(Connector.class); + categoryId = connector.categoryId(); + } + if (categoryId != null) { + dataId = StateHelper.getStateId(pluginClass, categoryId, data.stateId()); + } + } + else { + if (field.getDeclaringClass().isAnnotationPresent(Action.class)) { + dataId = DataHelper._getDataId(ActionHelper.getActionId(pluginClass, field), data.id().isEmpty() ? field.getName() : data.id()); + } + else if (field.getDeclaringClass().isAnnotationPresent(Connector.class)) { + dataId = DataHelper._getDataId(ConnectorHelper.getConnectorId(pluginClass, field), data.id().isEmpty() ? field.getName() : data.id()); + } + } + } + + return dataId; + } + /** * Internal - Get the formatted Data Id * diff --git a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/PluginHelper.java b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/PluginHelper.java index b4706d68..fc777ec1 100644 --- a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/PluginHelper.java +++ b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/PluginHelper.java @@ -29,21 +29,25 @@ * Touch Portal Plugin Plugin Helper */ public class PluginHelper { - public static final String SDK = "sdk"; + public static final String API = "api"; public static final String VERSION = "version"; public static final String NAME = GenericHelper.NAME; public static final String ID = GenericHelper.ID; public static final String CONFIGURATION = "configuration"; public static final String CONFIGURATION_COLOR_DARK = "colorDark"; public static final String CONFIGURATION_COLOR_LIGHT = "colorLight"; + public static final String CONFIGURATION_PARENT_CATEGORY = "parentCategory"; public static final String PLUGIN_START_COMMAND = "plugin_start_cmd"; + public static final String PLUGIN_START_COMMAND_SUFFIX_WIN = "_windows"; + public static final String PLUGIN_START_COMMAND_SUFFIX_MACOS = "_mac"; + public static final String PLUGIN_START_COMMAND_SUFFIX_LINUX = "_linux"; public static final String CATEGORIES = "categories"; public static final String SETTINGS = "settings"; /** * Touch Portal Plugin System version */ - public static final int TOUCH_PORTAL_PLUGIN_VERSION = 6; + public static final int TOUCH_PORTAL_PLUGIN_VERSION = 7; /** * Argument passed to the jar to start the plugin */ @@ -52,6 +56,10 @@ public class PluginHelper { * Touch Portal Plugin System TP_PLUGIN_FOLDER */ public static final String TP_PLUGIN_FOLDER = "%TP_PLUGIN_FOLDER%"; + /** + * Touch Portal Plugin System TP_JAVA_FILE + */ + public static final String TP_JAVA = "%TP_JAVA_FILE%"; /** * Touch Portal entry file */ diff --git a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ReceivedMessageHelper.java b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ReceivedMessageHelper.java index 2af3cedc..b6e7c96a 100644 --- a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ReceivedMessageHelper.java +++ b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/ReceivedMessageHelper.java @@ -40,7 +40,7 @@ public class ReceivedMessageHelper { public static final String TYPE_CONNECTOR_CHANGE = "connectorChange"; public static final String TYPE_SHORT_CONNECTOR_ID_NOTIFICATION = "shortConnectorIdNotification"; public static final String TYPE_INFO = "info"; - public static final String TYPE_LIST_CHANGE = "listChange"; + public static final String TYPE_LIST_CHANGED = "listChange"; public static final String TYPE_CLOSE_PLUGIN = "closePlugin"; public static final String TYPE_BROADCAST = "broadcast"; public static final String TYPE_SETTINGS = "settings"; @@ -87,7 +87,7 @@ public static boolean isTypeAction(JsonObject jsonMessage) { * @return boolean isMessageAListChange */ public static boolean isTypeListChange(JsonObject jsonMessage) { - return ReceivedMessageHelper.TYPE_LIST_CHANGE.equals(ReceivedMessageHelper.getType(jsonMessage)); + return ReceivedMessageHelper.TYPE_LIST_CHANGED.equals(ReceivedMessageHelper.getType(jsonMessage)); } /** diff --git a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/SentMessageHelper.java b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/SentMessageHelper.java index a852b127..afa854a1 100644 --- a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/SentMessageHelper.java +++ b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/SentMessageHelper.java @@ -34,6 +34,7 @@ public class SentMessageHelper { public static final String TYPE_SETTING_UPDATE = "settingUpdate"; public static final String TYPE_SHOW_NOTIFICATION = "showNotification"; public static final String TYPE_CONNECTOR_UPDATE = "connectorUpdate"; + public static final String TYPE_TRIGGER_EVENT = "triggerEvent"; public static final String INSTANCE_ID = "instanceId"; public static final String ID = GenericHelper.ID; public static final String VALUE = GenericHelper.VALUE; @@ -48,5 +49,8 @@ public class SentMessageHelper { public static final String CONNECTOR_ID = "connectorId"; public static final String SHORT_ID = "shortId"; public static final String PARENT_GROUP = "parentGroup"; + public static final String FORCE_UPDATE = "forceUpdate"; + public static final String EVENT_ID = "eventId"; + public static final String STATES = "states"; } \ No newline at end of file diff --git a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/SettingHelper.java b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/SettingHelper.java index e559e987..a78eff38 100644 --- a/Helpers/src/main/java/com/christophecvb/touchportal/helpers/SettingHelper.java +++ b/Helpers/src/main/java/com/christophecvb/touchportal/helpers/SettingHelper.java @@ -39,6 +39,13 @@ public class SettingHelper { public static final String MIN_VALUE = "minValue"; public static final String MAX_VALUE = "maxValue"; public static final String IS_READ_ONLY = "readOnly"; + public static final String TOOLTIP = "tooltip"; + + public static class Tooltip { + public static final String TITLE = "title"; + public static final String BODY = "body"; + public static final String DOC_URL = "docUrl"; + } /** * Get the generated Setting Name diff --git a/Library/build.gradle b/Library/build.gradle index 31acac36..2125853f 100644 --- a/Library/build.gradle +++ b/Library/build.gradle @@ -81,11 +81,11 @@ dependencies { api project(':Annotations') api project(':Helpers') - api group: 'com.google.code.gson', name: 'gson', version: '2.9.0' + api libs.gson - implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.3' + implementation libs.okhttp - testImplementation group: 'junit', name: 'junit', version: '4.13.2' + testImplementation libs.junit testAnnotationProcessor project(':AnnotationsProcessor') } diff --git a/Library/src/main/java/com/christophecvb/touchportal/TPAction.java b/Library/src/main/java/com/christophecvb/touchportal/TPAction.java new file mode 100644 index 00000000..054a147f --- /dev/null +++ b/Library/src/main/java/com/christophecvb/touchportal/TPAction.java @@ -0,0 +1,12 @@ +package com.christophecvb.touchportal; + +public abstract class TPAction extends TPInvokable { + + public TPAction(T touchPortalPlugin) { + super(touchPortalPlugin); + } + + protected Boolean isBeingHeld(String actionId) { + return this.touchPortalPlugin.isActionBeingHeld(actionId); + } +} \ No newline at end of file diff --git a/Library/src/main/java/com/christophecvb/touchportal/TPConnector.java b/Library/src/main/java/com/christophecvb/touchportal/TPConnector.java new file mode 100644 index 00000000..8e76c7af --- /dev/null +++ b/Library/src/main/java/com/christophecvb/touchportal/TPConnector.java @@ -0,0 +1,8 @@ +package com.christophecvb.touchportal; + +public abstract class TPConnector extends TPInvokable { + + public TPConnector(T touchPortalPlugin) { + super(touchPortalPlugin); + } +} \ No newline at end of file diff --git a/Library/src/main/java/com/christophecvb/touchportal/TPInvokable.java b/Library/src/main/java/com/christophecvb/touchportal/TPInvokable.java new file mode 100644 index 00000000..8a5ae7f3 --- /dev/null +++ b/Library/src/main/java/com/christophecvb/touchportal/TPInvokable.java @@ -0,0 +1,15 @@ +package com.christophecvb.touchportal; + +import com.christophecvb.touchportal.model.TPListChangedMessage; + +abstract class TPInvokable { + protected final T touchPortalPlugin; + + public TPInvokable(T touchPortalPlugin) { + this.touchPortalPlugin = touchPortalPlugin; + } + + public abstract void onInvoke(); + + public abstract void onListChanged(TPListChangedMessage tpListChangedMessage); +} \ No newline at end of file diff --git a/Library/src/main/java/com/christophecvb/touchportal/TouchPortalPlugin.java b/Library/src/main/java/com/christophecvb/touchportal/TouchPortalPlugin.java index 37865d03..7beec982 100644 --- a/Library/src/main/java/com/christophecvb/touchportal/TouchPortalPlugin.java +++ b/Library/src/main/java/com/christophecvb/touchportal/TouchPortalPlugin.java @@ -27,19 +27,17 @@ import com.google.gson.*; import okhttp3.*; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.io.*; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; +import java.lang.reflect.*; import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.*; @@ -135,10 +133,18 @@ public abstract class TouchPortalPlugin { * Current Held Actions States */ private final HashMap heldActionsStates = new HashMap<>(); + /** + * Map containing image urls and their base64 representation + */ + private final HashMap base64Images = new HashMap<>(); /** * Executor Service for callbacks */ private final ExecutorService callbacksExecutor; + /** + * Registered {@link TPInvokable}s + */ + private final HashMap> registeredInvokables = new HashMap<>(); /** * Internal Gson Serializer/Deserializer @@ -184,7 +190,7 @@ private Thread createListenerThread() { TPMessageDeserializer tpMessageDeserializer = new TPMessageDeserializer(); tpMessageDeserializer.registerTPMessageType(ReceivedMessageHelper.TYPE_CLOSE_PLUGIN, TPClosePluginMessage.class); tpMessageDeserializer.registerTPMessageType(ReceivedMessageHelper.TYPE_INFO, TPInfoMessage.class); - tpMessageDeserializer.registerTPMessageType(ReceivedMessageHelper.TYPE_LIST_CHANGE, TPListChangeMessage.class); + tpMessageDeserializer.registerTPMessageType(ReceivedMessageHelper.TYPE_LIST_CHANGED, TPListChangedMessage.class); tpMessageDeserializer.registerTPMessageType(ReceivedMessageHelper.TYPE_BROADCAST, TPBroadcastMessage.class); tpMessageDeserializer.registerTPMessageType(ReceivedMessageHelper.TYPE_SETTINGS, TPSettingsMessage.class); tpMessageDeserializer.registerTPMessageType(ReceivedMessageHelper.TYPE_ACTION, TPActionMessage.class); @@ -224,21 +230,40 @@ private void onMessage(String socketMessage) throws SocketException, JsonParseEx this.updateSettingFields(this.tpInfoMessage.settings); if (this.touchPortalPluginListener != null) { - this.touchPortalPluginListener.onInfo(this.tpInfoMessage); + this.callbacksExecutor.submit(() -> { + this.touchPortalPluginListener.onInfo(this.tpInfoMessage); + }); } break; - case ReceivedMessageHelper.TYPE_LIST_CHANGE: - TPListChangeMessage listChangeMessage = (TPListChangeMessage) tpMessage; + case ReceivedMessageHelper.TYPE_LIST_CHANGED: + TPListChangedMessage tpListChangedMessage = (TPListChangedMessage) tpMessage; + if (this.registeredInvokables.containsKey(tpListChangedMessage.actionId)) { + Class invokableClass = this.registeredInvokables.get(tpListChangedMessage.actionId); + try { + TPInvokable tpInvokable = this.instantiateTPInvokable(invokableClass); + + this.callbacksExecutor.submit(() -> { + tpInvokable.onListChanged(tpListChangedMessage); + }); + } + catch (ReflectiveOperationException e) { + TouchPortalPlugin.LOGGER.log(Level.WARNING, "Invokable could not be created or its onListChanged could not be invoked", e); + } + } if (this.touchPortalPluginListener != null) { - this.touchPortalPluginListener.onListChanged(listChangeMessage); + this.callbacksExecutor.submit(() -> { + this.touchPortalPluginListener.onListChanged(tpListChangedMessage); + }); } break; case ReceivedMessageHelper.TYPE_BROADCAST: TPBroadcastMessage tpBroadcastMessage = (TPBroadcastMessage) tpMessage; if (this.touchPortalPluginListener != null) { - this.touchPortalPluginListener.onBroadcast(tpBroadcastMessage); + this.callbacksExecutor.submit(() -> { + this.touchPortalPluginListener.onBroadcast(tpBroadcastMessage); + }); } break; @@ -248,14 +273,18 @@ private void onMessage(String socketMessage) throws SocketException, JsonParseEx this.updateSettingFields(tpSettingsMessage.settings); if (this.touchPortalPluginListener != null) { - this.touchPortalPluginListener.onSettings(tpSettingsMessage); + this.callbacksExecutor.submit(() -> { + this.touchPortalPluginListener.onSettings(tpSettingsMessage); + }); } break; case ReceivedMessageHelper.TYPE_NOTIFICATION_OPTION_CLICKED: TPNotificationOptionClickedMessage tpNotificationOptionClickedMessage = (TPNotificationOptionClickedMessage) tpMessage; if (this.touchPortalPluginListener != null) { - this.touchPortalPluginListener.onNotificationOptionClicked(tpNotificationOptionClickedMessage); + this.callbacksExecutor.submit(() -> { + this.touchPortalPluginListener.onNotificationOptionClicked(tpNotificationOptionClickedMessage); + }); } break; @@ -287,7 +316,9 @@ private void onMessage(String socketMessage) throws SocketException, JsonParseEx } if (!called) { if (this.touchPortalPluginListener != null) { - this.touchPortalPluginListener.onReceived(jsonMessage); + this.callbacksExecutor.submit(() -> { + this.touchPortalPluginListener.onReceived(jsonMessage); + }); } } } @@ -297,6 +328,13 @@ private void onMessage(String socketMessage) throws SocketException, JsonParseEx } } + private TPInvokable instantiateTPInvokable(Class invokableClass) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { + Class typedTouchPortalPlugin = (Class) ((ParameterizedType) invokableClass.getGenericSuperclass()).getActualTypeArguments()[0]; + Constructor constructor = invokableClass.getConstructor(typedTouchPortalPlugin); + TPInvokable tpInvokable = constructor.newInstance(this); + return tpInvokable; + } + private void updateSettingFields(HashMap settings) { Field[] pluginSettingFields = Arrays.stream(this.pluginClass.getDeclaredFields()).filter(field -> field.isAnnotationPresent(Setting.class)).toArray(Field[]::new); for (String settingName : settings.keySet()) { @@ -316,105 +354,194 @@ private void updateSettingFields(HashMap settings) { } private boolean onActionReceived(TPActionMessage tpActionMessage, JsonObject jsonAction, Boolean held) { - boolean called = false; + boolean invoked = false; if (tpActionMessage.actionId != null && !tpActionMessage.actionId.isEmpty()) { - Method[] pluginActionMethods = Arrays.stream(this.pluginClass.getDeclaredMethods()).filter(method -> method.isAnnotationPresent(Action.class)).toArray(Method[]::new); - for (Method method : pluginActionMethods) { - String methodActionId = ActionHelper.getActionId(this.pluginClass, method); - if (tpActionMessage.actionId.equals(methodActionId)) { - try { - Parameter[] parameters = method.getParameters(); - Object[] arguments = new Object[parameters.length]; - for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) { - Parameter parameter = parameters[parameterIndex]; - if (parameter.isAnnotationPresent(Data.class)) { - arguments[parameterIndex] = tpActionMessage.getTypedDataValue(this.pluginClass, method, parameter); - } - else if (parameter.getType().isAssignableFrom(JsonObject.class)) { - arguments[parameterIndex] = jsonAction; - } - else if (parameter.getType().isAssignableFrom(TPActionMessage.class)) { - arguments[parameterIndex] = tpActionMessage; - } - if (arguments[parameterIndex] == null) { - throw new MethodDataParameterException(method, parameter); - } + if (this.registeredInvokables.containsKey(tpActionMessage.actionId)) { + Class invokableClass = this.registeredInvokables.get(tpActionMessage.actionId); + try { + TPInvokable tpInvokable = this.instantiateTPInvokable(invokableClass); + + for (Field declaredField : invokableClass.getDeclaredFields()) { + if (declaredField.isAnnotationPresent(Data.class)) { + declaredField.setAccessible(true); + Object fieldValue = tpActionMessage.getTypedDataValue(this.pluginClass, declaredField); + declaredField.set(tpInvokable, fieldValue); } - this.heldActionsStates.put(tpActionMessage.actionId, held); - this.callbacksExecutor.submit(() -> { - try { - method.setAccessible(true); - method.invoke(this, arguments); - } - catch (Exception e) { - TouchPortalPlugin.LOGGER.log(Level.SEVERE, "Action method could not be invoked", e); + else if (declaredField.getType().isAssignableFrom(JsonObject.class)) { + declaredField.setAccessible(true); + declaredField.set(tpInvokable, jsonAction); + } + else if (declaredField.getType().isAssignableFrom(TPActionMessage.class)) { + declaredField.setAccessible(true); + declaredField.set(tpInvokable, tpActionMessage); + } + } + + this.heldActionsStates.put(tpActionMessage.actionId, held); + this.callbacksExecutor.submit(() -> { + try { + tpInvokable.onInvoke(); + } + catch (Exception e) { + TouchPortalPlugin.LOGGER.log(Level.SEVERE, "Action could not be invoked", e); + } + finally { + if (held == null || !held) { + this.heldActionsStates.remove(tpActionMessage.actionId); } - finally { - if (held == null || !held) { - this.heldActionsStates.remove(tpActionMessage.actionId); + } + }); + + invoked = true; + } + catch (ReflectiveOperationException e) { + TouchPortalPlugin.LOGGER.log(Level.WARNING, "Action could not be instanced", e); + } + } + else { + Method[] pluginActionMethods = Arrays.stream(this.pluginClass.getDeclaredMethods()).filter(method -> method.isAnnotationPresent(Action.class)).toArray(Method[]::new); + for (Method method : pluginActionMethods) { + String methodActionId = ActionHelper.getActionId(this.pluginClass, method); + if (tpActionMessage.actionId.equals(methodActionId)) { + try { + Parameter[] parameters = method.getParameters(); + Object[] arguments = new Object[parameters.length]; + for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) { + Parameter parameter = parameters[parameterIndex]; + if (parameter.isAnnotationPresent(Data.class)) { + arguments[parameterIndex] = tpActionMessage.getTypedDataValue(this.pluginClass, method, parameter); + } + else if (parameter.getType().isAssignableFrom(JsonObject.class)) { + arguments[parameterIndex] = jsonAction; + } + else if (parameter.getType().isAssignableFrom(TPActionMessage.class)) { + arguments[parameterIndex] = tpActionMessage; + } + if (arguments[parameterIndex] == null) { + throw new MethodDataParameterException(method, parameter); } } - }); - called = true; - } - catch (MethodDataParameterException e) { - TouchPortalPlugin.LOGGER.log(Level.WARNING, e.getMessage(), e); + this.heldActionsStates.put(tpActionMessage.actionId, held); + this.callbacksExecutor.submit(() -> { + try { + method.setAccessible(true); + method.invoke(this, arguments); + } + catch (Exception e) { + TouchPortalPlugin.LOGGER.log(Level.SEVERE, "Action method could not be invoked", e); + } + finally { + if (held == null || !held) { + this.heldActionsStates.remove(tpActionMessage.actionId); + } + } + }); + invoked = true; + } + catch (MethodDataParameterException e) { + TouchPortalPlugin.LOGGER.log(Level.WARNING, "Action method data parameters could not be retrieved", e); + } + break; } - break; } } } - return called; + return invoked; } - private boolean onConnectorChangeReceived(TPConnectorChangeMessage tpConnectorChangeMessage, JsonObject jsonAction) { - boolean called = false; + private boolean onConnectorChangeReceived(TPConnectorChangeMessage tpConnectorChangeMessage, JsonObject jsonConnectorChange) { + boolean invoked = false; if (tpConnectorChangeMessage.connectorId != null && !tpConnectorChangeMessage.connectorId.isEmpty()) { - Method[] pluginConnectorMethods = Arrays.stream(this.pluginClass.getDeclaredMethods()).filter(method -> method.isAnnotationPresent(Connector.class)).toArray(Method[]::new); - for (Method method : pluginConnectorMethods) { - String methodConnectorId = ConnectorHelper.getConnectorId(this.pluginClass, method); - if (tpConnectorChangeMessage.connectorId.equals(methodConnectorId)) { - try { - Parameter[] parameters = method.getParameters(); - Object[] arguments = new Object[parameters.length]; - for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) { - Parameter parameter = parameters[parameterIndex]; - if (parameter.isAnnotationPresent(Data.class)) { - arguments[parameterIndex] = tpConnectorChangeMessage.getTypedDataValue(this.pluginClass, method, parameter); - } - else if (parameter.isAnnotationPresent(ConnectorValue.class)) { - arguments[parameterIndex] = tpConnectorChangeMessage.value; - } - else if (parameter.getType().isAssignableFrom(JsonObject.class)) { - arguments[parameterIndex] = jsonAction; - } - else if (parameter.getType().isAssignableFrom(TPConnectorChangeMessage.class)) { - arguments[parameterIndex] = tpConnectorChangeMessage; - } - if (arguments[parameterIndex] == null) { - throw new MethodDataParameterException(method, parameter); - } + if (this.registeredInvokables.containsKey(tpConnectorChangeMessage.connectorId)) { + Class invokableClass = this.registeredInvokables.get(tpConnectorChangeMessage.connectorId); + try { + TPInvokable tpInvokable = this.instantiateTPInvokable(invokableClass); + + for (Field declaredField : invokableClass.getDeclaredFields()) { + if (declaredField.isAnnotationPresent(Data.class)) { + declaredField.setAccessible(true); + Object fieldValue = tpConnectorChangeMessage.getTypedDataValue(this.pluginClass, declaredField); + declaredField.set(tpInvokable, fieldValue); + } + else if (declaredField.isAnnotationPresent(ConnectorValue.class)) { + declaredField.setAccessible(true); + declaredField.set(tpInvokable, tpConnectorChangeMessage.value); + } + else if (declaredField.getType().isAssignableFrom(JsonObject.class)) { + declaredField.setAccessible(true); + declaredField.set(tpInvokable, jsonConnectorChange); + } + else if (declaredField.getType().isAssignableFrom(TPConnectorChangeMessage.class)) { + declaredField.setAccessible(true); + declaredField.set(tpInvokable, tpConnectorChangeMessage); } - this.currentConnectorValues.put(tpConnectorChangeMessage.getConstructedId(), tpConnectorChangeMessage.value); - this.callbacksExecutor.submit(() -> { - try { - method.setAccessible(true); - method.invoke(this, arguments); - } - catch (Exception e) { - TouchPortalPlugin.LOGGER.log(Level.SEVERE, "Connector method could not be invoked", e); - } - }); - called = true; } - catch (MethodDataParameterException e) { - TouchPortalPlugin.LOGGER.log(Level.WARNING, e.getMessage(), e); + + this.currentConnectorValues.put(tpConnectorChangeMessage.getConstructedId(), tpConnectorChangeMessage.value); + this.callbacksExecutor.submit(() -> { + try { + tpInvokable.onInvoke(); + } + catch (Exception e) { + TouchPortalPlugin.LOGGER.log(Level.SEVERE, "Connector could not be invoked", e); + } + }); + + invoked = true; + } + catch (ReflectiveOperationException e) { + TouchPortalPlugin.LOGGER.log(Level.WARNING, "Connector could not be created or invoked", e); + } + } + else { + Method[] pluginConnectorMethods = Arrays.stream(this.pluginClass.getDeclaredMethods()).filter(method -> method.isAnnotationPresent(Connector.class)).toArray(Method[]::new); + for (Method method : pluginConnectorMethods) { + String methodConnectorId = ConnectorHelper.getConnectorId(this.pluginClass, method); + if (tpConnectorChangeMessage.connectorId.equals(methodConnectorId)) { + try { + Parameter[] parameters = method.getParameters(); + Object[] arguments = new Object[parameters.length]; + for (int parameterIndex = 0; parameterIndex < parameters.length; parameterIndex++) { + Parameter parameter = parameters[parameterIndex]; + if (parameter.isAnnotationPresent(Data.class)) { + arguments[parameterIndex] = tpConnectorChangeMessage.getTypedDataValue(this.pluginClass, method, parameter); + } + else if (parameter.isAnnotationPresent(ConnectorValue.class)) { + arguments[parameterIndex] = tpConnectorChangeMessage.value; + } + else if (parameter.getType().isAssignableFrom(JsonObject.class)) { + arguments[parameterIndex] = jsonConnectorChange; + } + else if (parameter.getType().isAssignableFrom(TPConnectorChangeMessage.class)) { + arguments[parameterIndex] = tpConnectorChangeMessage; + } + if (arguments[parameterIndex] == null) { + throw new MethodDataParameterException(method, parameter); + } + } + + this.currentConnectorValues.put(tpConnectorChangeMessage.getConstructedId(), tpConnectorChangeMessage.value); + this.callbacksExecutor.submit(() -> { + try { + method.setAccessible(true); + method.invoke(this, arguments); + } + catch (Exception e) { + TouchPortalPlugin.LOGGER.log(Level.SEVERE, "Connector method could not be invoked", e); + } + }); + + invoked = true; + } + catch (MethodDataParameterException e) { + TouchPortalPlugin.LOGGER.log(Level.WARNING, e.getMessage(), e); + } + break; } - break; } } } - return called; + return invoked; } /** @@ -433,6 +560,16 @@ private boolean sendPair() { return paired; } + /** + * Register a {@link TPAction} or {@link TPConnector} + * + * @param invokableId String + * @param invokableClass Class<{@link TPInvokable}> + */ + protected void registerInvokable(String invokableId, Class invokableClass) { + this.registeredInvokables.put(invokableId, invokableClass); + } + /** * Get TPInfo * @@ -767,6 +904,7 @@ public boolean sendCreateState(String categoryId, String stateId, String parentG createStateMessage.addProperty(SentMessageHelper.ID, stateId); createStateMessage.addProperty(SentMessageHelper.DESCRIPTION, description); createStateMessage.addProperty(SentMessageHelper.DEFAULT_VALUE, valueStr); + createStateMessage.addProperty(SentMessageHelper.FORCE_UPDATE, true); if (parentGroup == null || parentGroup.isEmpty()) { classLoop: for (Class subClass : this.pluginClass.getDeclaredClasses()) { @@ -912,6 +1050,36 @@ public boolean sendShowNotification(String notificationId, String title, String return sent; } + /** + * Send a Trigger Event message to the Touch Portal Plugin System + * + * @param eventId String + * @param states Map<String, Object> Key value pair of state id and value + * @return Boolean sent + */ + public boolean sendTriggerEvent(String eventId, Map states) { + boolean sent = false; + if (eventId != null && !eventId.isEmpty()) { + JsonObject triggerEventMessage = new JsonObject(); + triggerEventMessage.addProperty(SentMessageHelper.TYPE, SentMessageHelper.TYPE_TRIGGER_EVENT); + triggerEventMessage.addProperty(SentMessageHelper.EVENT_ID, eventId); + + if (states != null && !states.isEmpty()) { + JsonObject jsonStates = new JsonObject(); + + states.forEach((id, value) -> { + jsonStates.addProperty(id, String.valueOf(value)); + }); + triggerEventMessage.add(SentMessageHelper.STATES, jsonStates); + } + + sent = this.send(triggerEventMessage); + TouchPortalPlugin.LOGGER.info("Trigger Event [" + eventId + "] Sent [" + sent + "]"); + } + + return sent; + } + /** * Send a Connector Update Message to the Touch Portal Plugin System * @@ -1197,6 +1365,41 @@ public Boolean isActionBeingHeld(String actionId) { return this.heldActionsStates.get(actionId); } + /** + * Convert an image url to a base64 representation + * + * @param imageUrl String + * @return String base64Image + */ + public String getBase64ImageFromUrl(String imageUrl) { + String base64Image = ""; + if (this.base64Images.containsKey(imageUrl)) { + base64Image = this.base64Images.get(imageUrl); + } + else { + ByteArrayOutputStream byteArrayOutputStream = null; + try { + BufferedImage bufferedImage = ImageIO.read(new URL(imageUrl)); + + ImageIO.write(bufferedImage, "jpg", byteArrayOutputStream = new ByteArrayOutputStream()); + base64Image = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); + this.base64Images.put(imageUrl, base64Image); + } + catch (Exception exception) { + LOGGER.warning(exception.getMessage() + " for Image URL: " + imageUrl); + } + finally { + if (byteArrayOutputStream != null) { + try { + byteArrayOutputStream.close(); + } + catch (IOException ignored) {} + } + } + } + return base64Image; + } + /** * Interface Definition for Callbacks */ @@ -1225,9 +1428,9 @@ public interface TouchPortalPluginListener { /** * Called when a List Change Message is received * - * @param tpListChangeMessage TPListChangeMessage + * @param tpListChangedMessage TPListChangeMessage */ - void onListChanged(TPListChangeMessage tpListChangeMessage); + void onListChanged(TPListChangedMessage tpListChangedMessage); /** * Called when a Broadcast Message is received @@ -1268,7 +1471,7 @@ public MethodDataParameterException(Method method, Parameter parameter) { /** * Custom ConsoleHandler */ - private static class CustomConsoleHandler extends ConsoleHandler { + public static class CustomConsoleHandler extends ConsoleHandler { public CustomConsoleHandler() { super(); setFormatter(new SimpleFormatter() { @@ -1278,7 +1481,7 @@ public CustomConsoleHandler() { public synchronized String format(LogRecord lr) { String[] path = lr.getSourceClassName().split("\\."); return String.format(format, - lr.getLevel().getLocalizedName(), + lr.getLevel().getName(), path[path.length - 1] + "." + lr.getSourceMethodName(), lr.getMessage() ); diff --git a/Library/src/main/java/com/christophecvb/touchportal/model/TPActionMessage.java b/Library/src/main/java/com/christophecvb/touchportal/model/TPActionMessage.java index ea6e1f05..2ecf1a4d 100644 --- a/Library/src/main/java/com/christophecvb/touchportal/model/TPActionMessage.java +++ b/Library/src/main/java/com/christophecvb/touchportal/model/TPActionMessage.java @@ -23,6 +23,7 @@ import com.christophecvb.touchportal.helpers.DataHelper; import com.christophecvb.touchportal.helpers.ReceivedMessageHelper; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; @@ -41,6 +42,10 @@ public Object getTypedDataValue(Class pluginClass, Method actionMethod, Param return this.getTypedDataValue(actionMethodParameter.getParameterizedType().getTypeName(), DataHelper.getDataId(pluginClass, actionMethod, actionMethodParameter)); } + public Object getTypedDataValue(Class pluginClass, Field actionField) { + return this.getTypedDataValue(actionField.getType().getTypeName(), DataHelper.getDataId(pluginClass, actionField)); + } + public Object getTypedDataValue(String actionDataType, String actionDataId) { Object value = null; diff --git a/Library/src/main/java/com/christophecvb/touchportal/model/TPConnectorChangeMessage.java b/Library/src/main/java/com/christophecvb/touchportal/model/TPConnectorChangeMessage.java index ae5db536..b865a254 100644 --- a/Library/src/main/java/com/christophecvb/touchportal/model/TPConnectorChangeMessage.java +++ b/Library/src/main/java/com/christophecvb/touchportal/model/TPConnectorChangeMessage.java @@ -24,6 +24,7 @@ import com.christophecvb.touchportal.helpers.DataHelper; import com.christophecvb.touchportal.helpers.ReceivedMessageHelper; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; @@ -44,6 +45,10 @@ public Object getTypedDataValue(Class pluginClass, Method actionMethod, Param return this.getTypedDataValue(connectorMethodParameter.getParameterizedType().getTypeName(), DataHelper.getDataId(pluginClass, actionMethod, connectorMethodParameter)); } + public Object getTypedDataValue(Class pluginClass, Field actionField) { + return this.getTypedDataValue(actionField.getType().getTypeName(), DataHelper.getDataId(pluginClass, actionField)); + } + public Object getTypedDataValue(String connectorDataType, String connectorDataId) { Object value = null; diff --git a/Library/src/main/java/com/christophecvb/touchportal/model/TPListChangeMessage.java b/Library/src/main/java/com/christophecvb/touchportal/model/TPListChangedMessage.java similarity index 94% rename from Library/src/main/java/com/christophecvb/touchportal/model/TPListChangeMessage.java rename to Library/src/main/java/com/christophecvb/touchportal/model/TPListChangedMessage.java index 83b2c773..f0243901 100644 --- a/Library/src/main/java/com/christophecvb/touchportal/model/TPListChangeMessage.java +++ b/Library/src/main/java/com/christophecvb/touchportal/model/TPListChangedMessage.java @@ -20,7 +20,7 @@ package com.christophecvb.touchportal.model; -public class TPListChangeMessage extends TPMessage { +public class TPListChangedMessage extends TPMessage { public String pluginId; public String actionId; public String listId; diff --git a/Library/src/test/java/com/christophecvb/touchportal/test/LibraryTests.java b/Library/src/test/java/com/christophecvb/touchportal/test/LibraryTests.java index 2f85477c..dc16d5ae 100644 --- a/Library/src/test/java/com/christophecvb/touchportal/test/LibraryTests.java +++ b/Library/src/test/java/com/christophecvb/touchportal/test/LibraryTests.java @@ -46,10 +46,14 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import static org.junit.Assert.*; public class LibraryTests { + private static final Logger LOGGER = Logger.getLogger(LibraryTests.class.getSimpleName()); + private static final long REASONABLE_TIME = 100; private ServerSocket serverSocket; private Socket serverSocketClient; private TouchPortalPluginTest touchPortalPluginTest; @@ -67,7 +71,7 @@ public void onInfo(TPInfoMessage tpInfoMessage) { } @Override - public void onListChanged(TPListChangeMessage tpListChangeMessage) { + public void onListChanged(TPListChangedMessage tpListChangedMessage) { } @Override @@ -83,6 +87,11 @@ public void onNotificationOptionClicked(TPNotificationOptionClickedMessage tpNot } }; + static { + LOGGER.setUseParentHandlers(false); + LOGGER.addHandler(new TouchPortalPlugin.CustomConsoleHandler()); + } + @Before public void initialize() throws IOException { // Mock Server @@ -113,7 +122,7 @@ private void serverSocketAccept() { } }).start(); try { - Thread.sleep(100); + Thread.sleep(REASONABLE_TIME); } catch (InterruptedException ignored) {} } @@ -137,7 +146,7 @@ public void close() { ioException.printStackTrace(); } try { - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); } catch (InterruptedException e) { e.printStackTrace(); @@ -149,8 +158,9 @@ public void close() { @Test public void testConnection() { + LOGGER.log(Level.FINE, "Now"); try { - Thread.sleep(500); + Thread.sleep(REASONABLE_TIME); } catch (InterruptedException e) { e.printStackTrace(); @@ -161,6 +171,7 @@ public void testConnection() { @Test public void testConnectionFail() { + LOGGER.log(Level.FINE, "Now"); this.close(); boolean connectedPairedAndListening = this.touchPortalPluginTest.connectThenPairAndListen(null); @@ -176,6 +187,7 @@ public void testConnectionFail() { @Test public void testMultipleConnect() { + LOGGER.log(Level.FINE, "Now"); // A connectThenPairAndListen is already done in the @Before assertTrue(this.touchPortalPluginTest.connectThenPairAndListen(null)); assertTrue(this.touchPortalPluginTest.connectThenPairAndListen(this.touchPortalPluginListener)); @@ -183,6 +195,7 @@ public void testMultipleConnect() { @Test public void testCloseAndReConnect() { + LOGGER.log(Level.FINE, "Now"); this.touchPortalPluginTest.close(null); boolean connectedPairedAndListening = this.touchPortalPluginTest.connectThenPairAndListen(null); @@ -192,6 +205,7 @@ public void testCloseAndReConnect() { @Test public void testConnectionNoListener() { + LOGGER.log(Level.FINE, "Now"); this.touchPortalPluginTest.close(null); boolean connectedPairedAndListening = this.touchPortalPluginTest.connectThenPairAndListen(null); @@ -201,19 +215,22 @@ public void testConnectionNoListener() { @Test public void testClose() { + LOGGER.log(Level.FINE, "Now"); this.touchPortalPluginTest.close(null); } @Test public void testServerSocketCloses() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); this.serverSocketClient.close(); this.serverSocket.close(); - Thread.sleep(100); + Thread.sleep(REASONABLE_TIME); assertFalse(this.touchPortalPluginTest.isConnected()); } @Test public void testSend() { + LOGGER.log(Level.FINE, "Now"); // Send State Update by ID from Constants assertTrue(this.touchPortalPluginTest.sendStateUpdate(TouchPortalPluginTestConstants.BaseCategory.States.CustomState.ID, "New Value 01")); @@ -226,6 +243,7 @@ public void testSend() { @Test public void testSendStates() { + LOGGER.log(Level.FINE, "Now"); assertFalse(this.touchPortalPluginTest.sendStateUpdate(null, null)); assertFalse(this.touchPortalPluginTest.sendStateUpdate("", null)); assertFalse(this.touchPortalPluginTest.sendStateUpdate("", "")); @@ -239,6 +257,7 @@ public void testSendStates() { @Test public void testSendChoices() { + LOGGER.log(Level.FINE, "Now"); assertFalse(this.touchPortalPluginTest.sendChoiceUpdate(null, null)); assertFalse(this.touchPortalPluginTest.sendChoiceUpdate("", new String[0])); assertFalse(this.touchPortalPluginTest.sendChoiceUpdate(null, new String[0])); @@ -253,6 +272,7 @@ public void testSendChoices() { @Test public void testSendSpecificChoices() { + LOGGER.log(Level.FINE, "Now"); assertFalse(this.touchPortalPluginTest.sendSpecificChoiceUpdate(null, null, null)); assertFalse(this.touchPortalPluginTest.sendSpecificChoiceUpdate("", null, null)); assertFalse(this.touchPortalPluginTest.sendSpecificChoiceUpdate("", "", null)); @@ -272,6 +292,7 @@ public void testSendSpecificChoices() { @Test public void testSendActionDataUpdate() { + LOGGER.log(Level.FINE, "Now"); HashMap props = new HashMap<>(); assertFalse(this.touchPortalPluginTest.sendActionDataUpdate(null, null, null)); assertFalse(this.touchPortalPluginTest.sendActionDataUpdate("", null, null)); @@ -290,6 +311,7 @@ public void testSendActionDataUpdate() { @Test public void testLastStateValue() { + LOGGER.log(Level.FINE, "Now"); String stateValue = System.currentTimeMillis() + ""; assertTrue(this.touchPortalPluginTest.sendStateUpdate(TouchPortalPluginTestConstants.BaseCategory.States.CustomState.ID, stateValue)); assertFalse(this.touchPortalPluginTest.sendStateUpdate(TouchPortalPluginTestConstants.BaseCategory.States.CustomState.ID, stateValue)); @@ -298,6 +320,7 @@ public void testLastStateValue() { @Test public void testShowNotification() { + LOGGER.log(Level.FINE, "Now"); assertFalse (this.touchPortalPluginTest.sendShowNotification(null, null, null, null)); assertFalse(this.touchPortalPluginTest.sendShowNotification("", null, null, null)); assertFalse(this.touchPortalPluginTest.sendShowNotification("", "", null, null)); @@ -313,6 +336,7 @@ public void testShowNotification() { @Test public void testDynamicStates() { + LOGGER.log(Level.FINE, "Now"); assertFalse(this.touchPortalPluginTest.sendCreateState(null, null, null, null)); assertFalse(this.touchPortalPluginTest.sendCreateState("", null, null, null)); @@ -362,6 +386,7 @@ public void testDynamicStates() { @Test public void testSendFail() { + LOGGER.log(Level.FINE, "Now"); this.touchPortalPluginTest.close(null); assertFalse(this.touchPortalPluginTest.sendStateUpdate(TouchPortalPluginTestConstants.BaseCategory.States.CustomState.ID, "New Value")); assertFalse(this.touchPortalPluginTest.sendChoiceUpdate("listId", null, true)); @@ -370,15 +395,30 @@ public void testSendFail() { assertFalse(this.touchPortalPluginTest.sendRemoveState("BaseCategory", "StateId")); } + @Test + public void testTriggerEvent() { + LOGGER.log(Level.FINE, "Now"); + assertFalse(this.touchPortalPluginTest.sendTriggerEvent(null, null)); + assertFalse(this.touchPortalPluginTest.sendTriggerEvent("", null)); + + assertTrue(this.touchPortalPluginTest.sendTriggerEvent(TouchPortalPluginTestConstants.BaseCategory.Events.CustomState.ID, null)); + + HashMap states = new HashMap<>(); + states.put(TouchPortalPluginTestConstants.BaseCategory.States.CustomState.ID, "StateValue"); + assertTrue(this.touchPortalPluginTest.sendTriggerEvent(TouchPortalPluginTestConstants.BaseCategory.Events.CustomState.ID, states)); + } + @Test public void testReceiveActionNoId() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_ACTION); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -386,13 +426,15 @@ public void testReceiveActionNoId() throws IOException, InterruptedException { @Test public void testReceiveConnectorNoId() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_CONNECTOR_CHANGE); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -400,14 +442,16 @@ public void testReceiveConnectorNoId() throws IOException, InterruptedException @Test public void testReceiveActionEmptyId() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_ACTION); jsonMessage.addProperty(ReceivedMessageHelper.ACTION_ID, ""); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -415,14 +459,16 @@ public void testReceiveActionEmptyId() throws IOException, InterruptedException @Test public void testReceiveConnectorEmptyId() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_CONNECTOR_CHANGE); jsonMessage.addProperty(ReceivedMessageHelper.CONNECTOR_ID, ""); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -430,6 +476,7 @@ public void testReceiveConnectorEmptyId() throws IOException, InterruptedExcepti @Test public void testReceiveShortConnectorIdNotification() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); Map dataReceived = new HashMap<>(); dataReceived.put(TouchPortalPluginTestConstants.BaseCategory.Connectors.ConnectorForSliderWithData.Text.ID + "0", "Text0!"); dataReceived.put(TouchPortalPluginTestConstants.BaseCategory.Connectors.ConnectorForSliderWithData.Text.ID, "Text!"); @@ -440,10 +487,11 @@ public void testReceiveShortConnectorIdNotification() throws IOException, Interr jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_SHORT_CONNECTOR_ID_NOTIFICATION); jsonMessage.addProperty(ReceivedMessageHelper.CONNECTOR_ID, ConnectorHelper.getConstructedId(TouchPortalPluginTestConstants.ID, TouchPortalPluginTestConstants.BaseCategory.Connectors.ConnectorForSliderWithData.ID, 0, dataReceived)); jsonMessage.addProperty(ReceivedMessageHelper.SHORT_ID, "SHORT_ID"); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -457,6 +505,7 @@ public void testReceiveShortConnectorIdNotification() throws IOException, Interr @Test public void testReceiveDummyWithDataTextAndNumberAction() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_ACTION); @@ -471,10 +520,11 @@ public void testReceiveDummyWithDataTextAndNumberAction() throws IOException, In numberDataItem.addProperty(ReceivedMessageHelper.ACTION_DATA_VALUE, 42); data.add(numberDataItem); jsonMessage.add(ActionHelper.DATA, data); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -482,6 +532,7 @@ public void testReceiveDummyWithDataTextAndNumberAction() throws IOException, In @Test public void testReceiveDummyWithDataFileAction() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_ACTION); @@ -497,55 +548,62 @@ public void testReceiveDummyWithDataFileAction() throws IOException, Interrupted directoryDataItem.addProperty(ReceivedMessageHelper.ACTION_DATA_VALUE, "/"); data.add(directoryDataItem); jsonMessage.add(ActionHelper.DATA, data); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); } @Test - public void testReceiveDummyDummyWithJsonObject() throws IOException, InterruptedException { + public void testReceiveDummyWithJsonObject() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_ACTION); jsonMessage.addProperty(ReceivedMessageHelper.ACTION_ID, TouchPortalPluginTestConstants.BaseCategory.Actions.DummyWithJsonObject.ID); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); } @Test - public void testReceiveDummyDummyWithTPActionMessage() throws IOException, InterruptedException { + public void testReceiveDummyWithTPActionMessage() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_ACTION); jsonMessage.addProperty(ReceivedMessageHelper.ACTION_ID, TouchPortalPluginTestConstants.BaseCategory.Actions.DummyWithTPActionMessage.ID); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); } @Test - public void testReceiveDummyDummyWithParam() throws IOException, InterruptedException { + public void testReceiveDummyWithParamNotData() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_ACTION); jsonMessage.addProperty(ReceivedMessageHelper.ACTION_ID, TouchPortalPluginTestConstants.BaseCategory.Actions.DummyWithParam.ID); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -553,15 +611,17 @@ public void testReceiveDummyDummyWithParam() throws IOException, InterruptedExce @Test public void testReceiveActionHoldableDownAndUp() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); JsonObject jsonMessageHoldDown = new JsonObject(); jsonMessageHoldDown.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessageHoldDown.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_HOLD_DOWN); jsonMessageHoldDown.addProperty(ReceivedMessageHelper.ACTION_ID, TouchPortalPluginTestConstants.BaseCategory.Actions.ActionHoldable.ID); + out.println(jsonMessageHoldDown); - Thread.sleep(150); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isActionBeingHeld(TouchPortalPluginTestConstants.BaseCategory.Actions.ActionHoldable.ID)); @@ -569,9 +629,10 @@ public void testReceiveActionHoldableDownAndUp() throws IOException, Interrupted jsonMessageHoldUp.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessageHoldUp.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_HOLD_UP); jsonMessageHoldUp.addProperty(ReceivedMessageHelper.ACTION_ID, TouchPortalPluginTestConstants.BaseCategory.Actions.ActionHoldable.ID); + out.println(jsonMessageHoldUp); - Thread.sleep(150); + Thread.sleep(REASONABLE_TIME); assertNull(this.touchPortalPluginTest.isActionBeingHeld(TouchPortalPluginTestConstants.BaseCategory.Actions.ActionHoldable.ID)); @@ -581,15 +642,17 @@ public void testReceiveActionHoldableDownAndUp() throws IOException, Interrupted @Test public void testReceiveActionHoldablePress() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); JsonObject jsonMessageHoldDown = new JsonObject(); jsonMessageHoldDown.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessageHoldDown.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_ACTION); jsonMessageHoldDown.addProperty(ReceivedMessageHelper.ACTION_ID, TouchPortalPluginTestConstants.BaseCategory.Actions.ActionHoldable.ID); + out.println(jsonMessageHoldDown); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertNull(this.touchPortalPluginTest.isActionBeingHeld(TouchPortalPluginTestConstants.BaseCategory.Actions.ActionHoldable.ID)); @@ -599,6 +662,7 @@ public void testReceiveActionHoldablePress() throws IOException, InterruptedExce @Test public void testReceiveConnectorForSlider() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); JsonObject jsonMessageConnectorForSlider = new JsonObject(); @@ -609,7 +673,7 @@ public void testReceiveConnectorForSlider() throws IOException, InterruptedExcep out.println(jsonMessageConnectorForSlider); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -617,6 +681,7 @@ public void testReceiveConnectorForSlider() throws IOException, InterruptedExcep @Test public void testReceiveConnectorForSliderWithData() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); JsonObject jsonMessageConnectorForSliderWithData = new JsonObject(); @@ -628,11 +693,12 @@ public void testReceiveConnectorForSliderWithData() throws IOException, Interrup JsonObject dataText = new JsonObject(); dataText.addProperty(ReceivedMessageHelper.ACTION_DATA_ID, TouchPortalPluginTestConstants.BaseCategory.Connectors.ConnectorForSliderWithData.Text.ID); dataText.addProperty(ReceivedMessageHelper.ACTION_DATA_VALUE, "Sliding!"); + data.add(dataText); jsonMessageConnectorForSliderWithData.add(ConnectorHelper.DATA, data); out.println(jsonMessageConnectorForSliderWithData); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -640,6 +706,7 @@ public void testReceiveConnectorForSliderWithData() throws IOException, Interrup @Test public void testUpdateConnectorValue() { + LOGGER.log(Level.FINE, "Now"); HashMap data = new HashMap<>(); data.put("dataId", "value"); assertFalse(this.touchPortalPluginTest.sendConnectorUpdate(null, null, null, null)); @@ -658,6 +725,7 @@ public void testUpdateConnectorValue() { @Test public void testReceiveConnectorForSliderWithNonData() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); JsonObject jsonMessageConnectorForSliderWithNonData = new JsonObject(); @@ -668,7 +736,7 @@ public void testReceiveConnectorForSliderWithNonData() throws IOException, Inter out.println(jsonMessageConnectorForSliderWithNonData); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -676,13 +744,15 @@ public void testReceiveConnectorForSliderWithNonData() throws IOException, Inter @Test public void testReceiveListChange() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); - jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_LIST_CHANGE); + jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_LIST_CHANGED); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -690,14 +760,16 @@ public void testReceiveListChange() throws IOException, InterruptedException { @Test public void testReceiveListChangeNoListener() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); this.touchPortalPluginTest.connectThenPairAndListen(null); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); - jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_LIST_CHANGE); + jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_LIST_CHANGED); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -705,14 +777,16 @@ public void testReceiveListChangeNoListener() throws IOException, InterruptedExc @Test public void testReceiveActionNoListener() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); this.touchPortalPluginTest.connectThenPairAndListen(null); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_ACTION); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -720,13 +794,15 @@ public void testReceiveActionNoListener() throws IOException, InterruptedExcepti @Test public void testReceiveBadPlugin() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, "falsePluginId"); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_ACTION); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -734,12 +810,14 @@ public void testReceiveBadPlugin() throws IOException, InterruptedException { @Test public void testReceiveNoMessageType() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -747,13 +825,15 @@ public void testReceiveNoMessageType() throws IOException, InterruptedException @Test public void testReceiveUnknownMessageType() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, "unknown"); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -761,6 +841,7 @@ public void testReceiveUnknownMessageType() throws IOException, InterruptedExcep @Test public void testReceiveInfo() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); TPInfoMessage sentTPInfoMessage = new TPInfoMessage(); sentTPInfoMessage.status = "paired"; sentTPInfoMessage.sdkVersion = 3L; @@ -782,7 +863,7 @@ public void testReceiveInfo() throws IOException, InterruptedException { PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -799,13 +880,15 @@ public void testReceiveInfo() throws IOException, InterruptedException { @Test public void testReceiveInfoMissingPropsAndNoListener() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); this.touchPortalPluginTest.connectThenPairAndListen(null); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_INFO); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -821,14 +904,16 @@ public void testReceiveInfoMissingPropsAndNoListener() throws IOException, Inter @Test public void testReceiveClose() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_CLOSE_PLUGIN); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); // Wait for the listenerThread to catch up - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertFalse(this.touchPortalPluginTest.isConnected()); assertFalse(this.touchPortalPluginTest.isListening()); @@ -836,10 +921,11 @@ public void testReceiveClose() throws IOException, InterruptedException { @Test public void testReceiveJSONFail() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println("Not a JSON Object"); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -847,10 +933,11 @@ public void testReceiveJSONFail() throws IOException, InterruptedException { @Test public void testReceiveEmpty() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -858,6 +945,7 @@ public void testReceiveEmpty() throws IOException, InterruptedException { @Test public void testReceivePart() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.print("This"); out.print("is"); @@ -865,7 +953,7 @@ public void testReceivePart() throws IOException, InterruptedException { out.print("data"); out.println(); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -873,25 +961,30 @@ public void testReceivePart() throws IOException, InterruptedException { @Test public void testReceiveBroadcast() throws IOException { + LOGGER.log(Level.FINE, "Now"); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_BROADCAST); jsonMessage.addProperty(ReceivedMessageHelper.EVENT, ReceivedMessageHelper.EVENT_PAGE_CHANGE); jsonMessage.addProperty(ReceivedMessageHelper.PAGE_NAME, "Page ONE"); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); } @Test public void testReceiveBroadcastMissingPropsAndNoListener() throws IOException { + LOGGER.log(Level.FINE, "Now"); this.touchPortalPluginTest.connectThenPairAndListen(null); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_BROADCAST); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); } @Test public void testReceiveSettings() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); JsonArray jsonSettings = new JsonArray(); JsonObject jsonSettingIP = new JsonObject(); @@ -911,7 +1004,7 @@ public void testReceiveSettings() throws IOException, InterruptedException { PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -922,14 +1015,16 @@ public void testReceiveSettings() throws IOException, InterruptedException { @Test public void testReceiveSettingsNoListener() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); this.touchPortalPluginTest.connectThenPairAndListen(null); JsonObject jsonMessage = new JsonObject(); jsonMessage.addProperty(ReceivedMessageHelper.PLUGIN_ID, TouchPortalPluginTestConstants.ID); jsonMessage.addProperty(ReceivedMessageHelper.TYPE, ReceivedMessageHelper.TYPE_SETTINGS); + PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertTrue(this.touchPortalPluginTest.isConnected()); assertTrue(this.touchPortalPluginTest.isListening()); @@ -937,6 +1032,7 @@ public void testReceiveSettingsNoListener() throws IOException, InterruptedExcep @Test public void testSendSettingUpdate() throws IOException, InterruptedException { + LOGGER.log(Level.FINE, "Now"); assertFalse(this.touchPortalPluginTest.sendSettingUpdate("DOES NOT EXISTS", "VALUE", false)); TPInfoMessage sentTPInfoMessage = new TPInfoMessage(); @@ -958,7 +1054,7 @@ public void testSendSettingUpdate() throws IOException, InterruptedException { PrintWriter out = new PrintWriter(this.serverSocketClient.getOutputStream(), true); out.println(jsonMessage); - Thread.sleep(10); + Thread.sleep(REASONABLE_TIME); assertFalse(this.touchPortalPluginTest.sendSettingUpdate(null, null, false)); assertFalse(this.touchPortalPluginTest.sendSettingUpdate("", null, false)); @@ -981,6 +1077,7 @@ public void testSendSettingUpdate() throws IOException, InterruptedException { @Test public void testAnnotations() { + LOGGER.log(Level.FINE, "Now"); assertEquals(TouchPortalPluginTestConstants.ID, "com.christophecvb.touchportal.test.TouchPortalPluginTest"); assertEquals(TouchPortalPluginTestConstants.BaseCategory.ID, "com.christophecvb.touchportal.test.TouchPortalPluginTest.BaseCategory"); assertEquals(TouchPortalPluginTestConstants.BaseCategory.Actions.DummyWithDataTextAndNumber.ID, "com.christophecvb.touchportal.test.TouchPortalPluginTest.BaseCategory.action.dummyWithDataTextAndNumber"); @@ -991,6 +1088,7 @@ public void testAnnotations() { @Test public void testEntryTPAndConstants() throws IOException { + LOGGER.log(Level.FINE, "Now"); File testGeneratedResourcesDirectory = new File("../../../../build/generated/sources/annotationProcessor/java/test/resources"); BufferedReader reader = Files.newBufferedReader(Paths.get(new File(testGeneratedResourcesDirectory.getAbsolutePath() + "/entry.tp").getAbsolutePath())); @@ -1070,6 +1168,7 @@ public void testEntryTPAndConstants() throws IOException { @Test public void testProperties() { + LOGGER.log(Level.FINE, "Now"); // Test reloadProperties without a Properties File assertFalse(this.touchPortalPluginTest.reloadProperties()); @@ -1098,11 +1197,13 @@ public void testProperties() { @Test public void testPropertiesFail() { + LOGGER.log(Level.FINE, "Now"); assertFalse(this.touchPortalPluginTest.loadProperties("doesNot.exists")); } @Test public void testPropertiesAccess() { + LOGGER.log(Level.FINE, "Now"); // No Loaded Properties File assertNull(this.touchPortalPluginTest.removeProperty("non existent")); assertNull(this.touchPortalPluginTest.getProperty("non existent")); @@ -1111,6 +1212,7 @@ public void testPropertiesAccess() { @Test public void testIsUpdateAvailable() { + LOGGER.log(Level.FINE, "Now"); assertFalse(this.touchPortalPluginTest.isUpdateAvailable("", 0)); assertFalse(this.touchPortalPluginTest.isUpdateAvailable("https://raw.githubusercontent.com/ChristopheCVB/TouchPortalPluginSDK/master/Library/src/test/resources/TouchPortalPluginTest/plugin.config", 1)); // Uses plugin.version assertTrue(this.touchPortalPluginTest.isUpdateAvailable("https://raw.githubusercontent.com/ChristopheCVB/TouchPortalPluginSDK/master/Library/src/test/resources/TouchPortalPluginTest/plugin.config", 0)); // Uses plugin.version @@ -1118,6 +1220,7 @@ public void testIsUpdateAvailable() { @Test public void testOAuth2() throws IOException { + LOGGER.log(Level.FINE, "Now"); String host = "localhost"; String callbackPath = "/oauth"; int port = -1; diff --git a/Library/src/test/resources/TouchPortalPluginTest/plugin.config b/Library/src/test/resources/TouchPortalPluginTest/plugin.config index 09a26035..ec09544b 100644 --- a/Library/src/test/resources/TouchPortalPluginTest/plugin.config +++ b/Library/src/test/resources/TouchPortalPluginTest/plugin.config @@ -1,4 +1,4 @@ #TouchPortalPluginTest -#Mon May 23 10:54:55 CEST 2022 +#Sun Dec 04 22:41:39 CET 2022 samplekey=Sample Value plugin.version=1 diff --git a/Packager/build.gradle b/Packager/build.gradle index 42d58ab0..a24250cb 100644 --- a/Packager/build.gradle +++ b/Packager/build.gradle @@ -1,13 +1,12 @@ plugins { - id 'groovy-gradle-plugin' - id 'com.gradle.plugin-publish' version '0.15.0' - id 'maven-publish' + id 'com.gradle.plugin-publish' version '1.0.0' id 'signing' + id 'groovy-gradle-plugin' } group 'com.christophecvb.touchportal' def localArchiveBaseName = 'plugin-packager' -version versionName +version isRelease ? versionName : versionName.replace('-SNAPSHOT', '-' + System.currentTimeSeconds()) pluginBundle { website = 'https://github.com/ChristopheCVB/TouchPortalPluginSDK' @@ -68,7 +67,7 @@ publishing { repositories { maven { - url = version.endsWith('SNAPSHOT') ? 'https://s01.oss.sonatype.org/content/repositories/snapshots/' : 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/' + url = version.contains('-') ? 'https://s01.oss.sonatype.org/content/repositories/snapshots/' : 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/' credentials { username = envOrPropOrEmpty('OSSRH_USERNAME') password = envOrPropOrEmpty('OSSRH_PASSWORD') diff --git a/Packager/src/main/groovy/com/christophecvb/touchportal/packager/TouchPortalPluginPackager.groovy b/Packager/src/main/groovy/com/christophecvb/touchportal/packager/TouchPortalPluginPackager.groovy index 453e64e7..c111e8c1 100644 --- a/Packager/src/main/groovy/com/christophecvb/touchportal/packager/TouchPortalPluginPackager.groovy +++ b/Packager/src/main/groovy/com/christophecvb/touchportal/packager/TouchPortalPluginPackager.groovy @@ -17,8 +17,9 @@ class TouchPortalPluginPackager implements Plugin { project.tasks.withType(JavaCompile) { task -> task.doFirst { - println('Adding -parameters to Compiler Args') + println('Adding -parameters to Compiler Args and setting encoding to UTF-8') options.compilerArgs.add('-parameters') + options.encoding = "UTF-8" } } @@ -53,6 +54,8 @@ class TouchPortalPluginPackager implements Plugin { } def copyResources = project.tasks.register('copyResources', Copy) { + dependsOn project.processResources + group = 'Touch Portal Plugin' from(project.file("${project.buildDir}/resources/main/")) into("${project.buildDir}/plugin/${extension.mainClassSimpleName.get()}/") diff --git a/README.md b/README.md index 7546e47c..6445d727 100644 --- a/README.md +++ b/README.md @@ -18,26 +18,24 @@ Once you have cloned this project, you can run the `gradlew javaDoc` and browse ## Releases -Latest is 8.0.0 - -Go to [releases](https://github.com/ChristopheCVB/TouchPortalPluginSDK/releases) +Go to [releases](https://github.com/ChristopheCVB/TouchPortalPluginSDK/releases) to check which is the latest ### Maven Central -Latest version is `8.0.0` - -Prior versions were not published to Maven Central +Versions before `7.0.0` were not published to Maven Central #### Gradle ```groovy plugins { - id 'com.christophecvb.touchportal.plugin-packager' version '8.0.0' + id 'com.christophecvb.touchportal.plugin-packager' version 'X.Y.Z' } +tpPlugin.mainClassSimpleName = 'MyTouchPortalPlugin' + dependencies { - implementation 'com.christophecvb.touchportal:plugin-sdk:8.0.0' - annotationProcessor 'com.christophecvb.touchportal:plugin-sdk-annotations-processor:8.0.0' + implementation 'com.christophecvb.touchportal:plugin-sdk:X.Y.Z' + annotationProcessor 'com.christophecvb.touchportal:plugin-sdk-annotations-processor:X.Y.Z' } ``` @@ -93,7 +91,7 @@ public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPorta /** * Called when a List Change Message is received */ - public void onListChanged(TPListChangeMessage tpListChangeMessage) { } + public void onListChanged(TPListChangeMessage tpListChangedMessage) { } /** * Called when a Broadcast Message is received @@ -105,16 +103,15 @@ public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPorta */ public void onSettings(TPSettingsMessage tpSettingsMessage) { } - /** - * Called when a Notification Option Clicked Message is received - */ - public void onNotificationOptionClicked(TPNotificationOptionClickedMessage tpNotificationOptionClickedMessage) {} + /** + * Called when a Notification Option Clicked Message is received + */ + public void onNotificationOptionClicked(TPNotificationOptionClickedMessage tpNotificationOptionClickedMessage) { } } ``` ## Development and Interaction - -- The SDK will automatically callback your action methods if they only contain `@Data` annotated parameters +The SDK will automatically invoke your action methods if they contain only `@Data` annotated parameters ```java public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPortalPlugin.TouchPortalPluginListener { @@ -127,14 +124,14 @@ public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPorta */ @Action(description = "Long Description of Dummy Action with Data Text", format = "Set text to {$text$}", categoryId = "BaseCategory") private void actionWithText(@Data String text) { - TouchPortalSamplePlugin.LOGGER.log(Level.INFO, "Action actionWithText received: " + text); + MyTouchPortalPlugin.LOGGER.log(Level.INFO, "Action actionWithText received: " + text); } // ... } ``` -- Otherwise, call your actions manually in the `onReceived(JsonObject jsonMessage)` method +Otherwise, call your actions manually in the `onReceived(JsonObject jsonMessage)` method ```java public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPortalPlugin.TouchPortalPluginListener { @@ -159,7 +156,7 @@ public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPorta } ``` -- Don't forget to initialize all your services once you receive the onInfo event. The TPInfoMessage will also contain the initial values of your settings. +Don't forget to initialize all your services once you receive the onInfo event. The TPInfoMessage will also contain the initial values of your settings. ```java public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPortalPlugin.TouchPortalPluginListener { @@ -167,7 +164,7 @@ public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPorta public void onInfo(TPInfoMessage tpInfoMessage) { // TPInfoMessage will contain the initial settings stored by TP - // -> Note that your annotated Settings fields will be up to date at this point + // -> Note that your annotated Settings fields will be up-to-date at this point // continue plugin initialization } @@ -176,7 +173,7 @@ public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPorta } ``` -- Finally, send messages back to TouchPortal when you want to update your states +Finally, send messages back to TouchPortal when you want to update your states ```java public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPortalPlugin.TouchPortalPluginListener { @@ -196,16 +193,24 @@ public class MyTouchPortalPlugin extends TouchPortalPlugin implements TouchPorta } ``` -## Use Annotations to describe your plugin and package it +## Use Annotations to describe your plugin + +The provided Annotations help you in the automatic generation of the `entry.tp` file (necessary for packaging and deployment of your plugin) + +Current supported annotations include: Plugin, Category, Action, Data, State, Event and Setting -- The provided Annotations help you in the automatic generation of the `entry.tp` file (necessary for packaging and deployment of your plugin) -- Current supported annotations include: Plugin, Category, Action, Data, State, Event and Setting -- More examples can be found in the sample modules +More examples can be found in the sample modules ```java // ... -@Plugin(version = BuildConfig.VERSION_CODE, colorDark = "#203060", colorLight = "#4070F0", name = "My Touch Portal Plugin") +@Plugin( + name = "My Touch Portal Plugin", + version = BuildConfig.VERSION_CODE, + colorDark = "#203060", + colorLight = "#4070F0", + parentCategory = ParentCategory.MISC +) public class MyTouchPortalPlugin extends TouchPortalPlugin { //... @@ -214,7 +219,11 @@ public class MyTouchPortalPlugin extends TouchPortalPlugin { * * @param text String */ - @Action(description = "Long Description of Dummy Action with Data", format = "Set text to {$text$}", categoryId = "BaseCategory") + @Action( + description = "Long Description of Dummy Action with Data", + format = "Set text to {$text$}", + categoryId = "BaseCategory" + ) private void dummyWithData(@Data String text) { LOGGER.log(Level.Info, "Action dummyWithData received: " + text); } @@ -240,13 +249,16 @@ public class MyTouchPortalPlugin extends TouchPortalPlugin { ## Prepackaging -- Add the Plugin icon and extra resources into the `src/main/resources/` directory of your module +Add the Plugin icon and extra resources into the `src/main/resources/` directory of your module -## Build +## Clean, Build and Package -- Use the common `gradlew clean` task to clean your build directories. -- Use the common `gradlew build` task to build your project with the Annotations. -- Use the `gradlew packagePlugin` task to pack your plugin into a `.tpp` file. Output files will be in your module `build/plugin` directory. +- Clean Project + - `gradlew clean` +- Build Project + - `gradlew build` +- Package Project + - `gradlew packagePlugin` task to pack your plugin into a `.tpp` file. Output files will be in your module's `build/plugin` directory. ## Debugging tips @@ -261,8 +273,4 @@ public class MyTouchPortalPlugin extends TouchPortalPlugin { - Main Class: `your.package.YourTouchPortalPlugin` - Arguments: `start` - Working Directory: `YourModule/build/plugin/YourTouchPortalPlugin` -[![Touch Portal Plugin SDK Gradle Application Configuration](https://raw.githubusercontent.com/ChristopheCVB/TouchPortalPluginSDK/master/resources/TP%20Plugin%20SDK%20Gradle%20Application%20Configuration.png)](#debugging-tips) - -## ROADMAP - -The roadmap can be found [here](https://github.com/ChristopheCVB/TouchPortalPluginSDK/projects/1) +![Touch Portal Plugin SDK Gradle Application Configuration](https://raw.githubusercontent.com/ChristopheCVB/TouchPortalPluginSDK/master/resources/TP%20Plugin%20SDK%20Gradle%20Application%20Configuration.png) diff --git a/SampleJava/build.gradle b/SampleJava/build.gradle index bcae488a..306d47b9 100644 --- a/SampleJava/build.gradle +++ b/SampleJava/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' - id 'com.github.gmazzo.buildconfig' version '3.0.0' - id 'com.christophecvb.touchportal.plugin-packager' version "$versionName+" + id 'com.github.gmazzo.buildconfig' version '3.1.0' + id 'com.christophecvb.touchportal.plugin-packager' version "+" } def mainClassSimpleName = 'TouchPortalSampleJavaPlugin' diff --git a/SampleJava/src/main/java/com/christophecvb/touchportal/samplejava/TouchPortalSampleJavaPlugin.java b/SampleJava/src/main/java/com/christophecvb/touchportal/samplejava/TouchPortalSampleJavaPlugin.java index 38ade3ff..0f968730 100644 --- a/SampleJava/src/main/java/com/christophecvb/touchportal/samplejava/TouchPortalSampleJavaPlugin.java +++ b/SampleJava/src/main/java/com/christophecvb/touchportal/samplejava/TouchPortalSampleJavaPlugin.java @@ -20,19 +20,27 @@ package com.christophecvb.touchportal.samplejava; +import com.christophecvb.touchportal.TouchPortalPlugin; import com.christophecvb.touchportal.annotations.*; import com.christophecvb.touchportal.helpers.PluginHelper; import com.christophecvb.touchportal.helpers.ReceivedMessageHelper; -import com.christophecvb.touchportal.TouchPortalPlugin; import com.christophecvb.touchportal.model.*; +import com.christophecvb.touchportal.samplejava.invokable.action.ExampleClassAction; +import com.christophecvb.touchportal.samplejava.invokable.connector.ExampleClassConnector; import com.google.gson.JsonObject; import java.io.File; +import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; -@SuppressWarnings("unused") -@Plugin(version = BuildConfig.VERSION_CODE, colorDark = "#203060", colorLight = "#4070F0", name = "Touch Portal Plugin Example") +@Plugin( + version = BuildConfig.VERSION_CODE, + colorDark = "#203060", + colorLight = "#4070F0", + name = BuildConfig.NAME, + parentCategory = ParentCategory.CONTENT +) public class TouchPortalSampleJavaPlugin extends TouchPortalPlugin implements TouchPortalPlugin.TouchPortalPluginListener { /** * Logger @@ -71,14 +79,18 @@ private enum Categories { /** * Setting of type text definition example */ - @Setting(name = "IP", defaultValue = "localhost", maxLength = 15) + @Setting(name = "IP", defaultValue = "localhost", maxLength = 15, tooltip = @Setting.Tooltip( + title = "IP address", + body = "ip address to connect to", + docUrl = "https://example.com" + )) private String ipSetting; /** * Setting of type number definition example */ @Setting(name = "Update Delay", defaultValue = "10", minValue = 10, maxValue = 30) - private int updateDelaySetting; + private int updateDelaySetting = 10; /** * Setting of type String and is read only definition example @@ -98,20 +110,29 @@ public static void main(String... args) { if (PluginHelper.COMMAND_START.equals(args[0])) { // Initialize the Plugin TouchPortalSampleJavaPlugin touchPortalSampleJavaPlugin = new TouchPortalSampleJavaPlugin(); + + // Register Invokable + touchPortalSampleJavaPlugin.registerInvokable(TouchPortalSampleJavaPluginConstants.BaseCategory.Actions.ExampleClassAction.ID, ExampleClassAction.class); + touchPortalSampleJavaPlugin.registerInvokable(TouchPortalSampleJavaPluginConstants.BaseCategory.Connectors.ExampleClassConnector.ID, ExampleClassConnector.class); + // Load a properties File touchPortalSampleJavaPlugin.loadProperties("plugin.config"); + // Get a property TouchPortalSampleJavaPlugin.LOGGER.log(Level.INFO, touchPortalSampleJavaPlugin.getProperty("samplekey")); + // Set a property touchPortalSampleJavaPlugin.setProperty("samplekey", "Value set from Plugin"); + // Store the properties touchPortalSampleJavaPlugin.storeProperties(); + // Initiate the connection with the Touch Portal Plugin System boolean connectedPairedAndListening = touchPortalSampleJavaPlugin.connectThenPairAndListen(touchPortalSampleJavaPlugin); if (connectedPairedAndListening) { // Update a State with the ID from the Generated Constants Class - boolean stateUpdated = touchPortalSampleJavaPlugin.sendStateUpdate(TouchPortalSampleJavaPluginConstants.BaseCategory.States.CustomStateWithEvent.ID, "2"); + touchPortalSampleJavaPlugin.sendStateUpdate(TouchPortalSampleJavaPluginConstants.BaseCategory.States.CustomStateWithEvent.ID, "2"); // Create a new State touchPortalSampleJavaPlugin.sendCreateState("BaseCategory", "createdState1", "Created State 01", System.currentTimeMillis() + "1"); @@ -136,8 +157,13 @@ public static void main(String... args) { touchPortalSampleJavaPlugin.sendConnectorUpdate(TouchPortalSampleJavaPluginConstants.ID, TouchPortalSampleJavaPluginConstants.BaseCategory.Connectors.ConnectorSimple.ID, 90, null); try { Thread.sleep(1000); - } catch (InterruptedException ignored) {} + } catch (InterruptedException ignored) { + } touchPortalSampleJavaPlugin.sendConnectorUpdate(TouchPortalSampleJavaPluginConstants.ID, TouchPortalSampleJavaPluginConstants.BaseCategory.Connectors.ConnectorSimple.ID, 50, null); + + HashMap states = new HashMap<>(); + states.put(TouchPortalSampleJavaPluginConstants.BaseCategory.States.CustomStateWithEvent.ID, "Value"); + touchPortalSampleJavaPlugin.sendTriggerEvent(TouchPortalSampleJavaPluginConstants.BaseCategory.Events.CustomStateWithEvent.ID, states); } } } @@ -147,6 +173,8 @@ public static void main(String... args) { * Action example with no parameter */ @Action(description = "Long Description of Action Simple", format = "Do a simple action", categoryId = "BaseCategory") + @ActionTranslation(language = Language.FRENCH, description = "Description longue de Action Simple", format = "Exécute une action simple", prefix = "Mon préfixe", name = "Action Simple") + @ActionTranslation(language = Language.PORTUGUESE, description = "Descrição longa da Acção Simples", format = "Realiza uma acção simples", prefix = "Meu prefixo", name = "Acção Simples") private void actionSimple() { TouchPortalSampleJavaPlugin.LOGGER.log(Level.INFO, "Action actionSimple received"); } @@ -298,7 +326,7 @@ public void onInfo(TPInfoMessage tpInfoMessage) { } @Override - public void onListChanged(TPListChangeMessage tpListChangeMessage) { + public void onListChanged(TPListChangedMessage tpListChangedMessage) { } @Override diff --git a/SampleJava/src/main/java/com/christophecvb/touchportal/samplejava/invokable/action/ExampleClassAction.java b/SampleJava/src/main/java/com/christophecvb/touchportal/samplejava/invokable/action/ExampleClassAction.java new file mode 100644 index 00000000..d484e1ee --- /dev/null +++ b/SampleJava/src/main/java/com/christophecvb/touchportal/samplejava/invokable/action/ExampleClassAction.java @@ -0,0 +1,43 @@ +package com.christophecvb.touchportal.samplejava.invokable.action; + +import com.christophecvb.touchportal.TPAction; +import com.christophecvb.touchportal.TouchPortalPlugin; +import com.christophecvb.touchportal.annotations.Action; +import com.christophecvb.touchportal.annotations.ActionTranslation; +import com.christophecvb.touchportal.annotations.Data; +import com.christophecvb.touchportal.annotations.Language; +import com.christophecvb.touchportal.model.TPListChangedMessage; +import com.christophecvb.touchportal.samplejava.TouchPortalSampleJavaPlugin; + +import java.util.logging.Logger; + +@Action( + name = "Example Class Action", + format = "Example Class Action with param {$param$}", + categoryId = "BaseCategory" +) +@ActionTranslation( + language = Language.FRENCH, + name = "Exemple d'Action via une Classe", + format = "Exemple d'Action via une Classe avec le paramètre {$param$}" +) +public class ExampleClassAction extends TPAction { + private final static Logger LOGGER = Logger.getLogger(TouchPortalPlugin.class.getName()); + + @Data + private String param; + + public ExampleClassAction(TouchPortalSampleJavaPlugin touchPortalPlugin) { + super(touchPortalPlugin); + } + + @Override + public void onInvoke() { + LOGGER.info("ExampleClassAction.onInvoke this.param=" + this.param); + } + + @Override + public void onListChanged(TPListChangedMessage tpListChangedMessage) { + LOGGER.info("ExampleClassAction.onListChanged"); + } +} diff --git a/SampleJava/src/main/java/com/christophecvb/touchportal/samplejava/invokable/connector/ExampleClassConnector.java b/SampleJava/src/main/java/com/christophecvb/touchportal/samplejava/invokable/connector/ExampleClassConnector.java new file mode 100644 index 00000000..8ffc3875 --- /dev/null +++ b/SampleJava/src/main/java/com/christophecvb/touchportal/samplejava/invokable/connector/ExampleClassConnector.java @@ -0,0 +1,39 @@ +package com.christophecvb.touchportal.samplejava.invokable.connector; + +import com.christophecvb.touchportal.TPConnector; +import com.christophecvb.touchportal.TouchPortalPlugin; +import com.christophecvb.touchportal.annotations.Connector; +import com.christophecvb.touchportal.annotations.ConnectorValue; +import com.christophecvb.touchportal.annotations.Data; +import com.christophecvb.touchportal.model.TPListChangedMessage; +import com.christophecvb.touchportal.samplejava.TouchPortalSampleJavaPlugin; + +import java.util.logging.Logger; + +@Connector( + name = "Example Class Connector", + categoryId = "BaseCategory", + format = "Connect Example Class Connector with param {$param$}" +) +public class ExampleClassConnector extends TPConnector { + private final static Logger LOGGER = Logger.getLogger(TouchPortalPlugin.class.getName()); + + @ConnectorValue + private Integer value; + @Data + private String param; + + public ExampleClassConnector(TouchPortalSampleJavaPlugin touchPortalPlugin) { + super(touchPortalPlugin); + } + + @Override + public void onInvoke() { + LOGGER.info("Example Class Connector with param=" + this.param + " and value=" + this.value); + } + + @Override + public void onListChanged(TPListChangedMessage tpListChangedMessage) { + LOGGER.info("ExampleClassConnector.onListChanged"); + } +} diff --git a/SampleKotlin/build.gradle b/SampleKotlin/build.gradle index 05162251..1faf69de 100644 --- a/SampleKotlin/build.gradle +++ b/SampleKotlin/build.gradle @@ -2,8 +2,8 @@ plugins { id 'org.jetbrains.kotlin.jvm' version '1.5.0' id 'org.jetbrains.kotlin.kapt' version '1.5.0' id 'java' - id 'com.github.gmazzo.buildconfig' version '3.0.0' - id 'com.christophecvb.touchportal.plugin-packager' version "$versionName+" + id 'com.github.gmazzo.buildconfig' version '3.1.0' + id 'com.christophecvb.touchportal.plugin-packager' version "+" } def mainClassSimpleName = 'TouchPortalSampleKotlinPlugin' @@ -24,7 +24,7 @@ buildConfig { } dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + implementation libs.kotlinstdlibjdk8 implementation project(':Library') diff --git a/SampleKotlin/src/main/kotlin/com/christophecvb/touchportal/samplekotlin/TouchPortalSampleKotlinPlugin.kt b/SampleKotlin/src/main/kotlin/com/christophecvb/touchportal/samplekotlin/TouchPortalSampleKotlinPlugin.kt index 470fa007..7e23c6c0 100644 --- a/SampleKotlin/src/main/kotlin/com/christophecvb/touchportal/samplekotlin/TouchPortalSampleKotlinPlugin.kt +++ b/SampleKotlin/src/main/kotlin/com/christophecvb/touchportal/samplekotlin/TouchPortalSampleKotlinPlugin.kt @@ -20,25 +20,27 @@ package com.christophecvb.touchportal.samplekotlin -import com.christophecvb.touchportal.annotations.Action -import com.christophecvb.touchportal.annotations.Category -import com.christophecvb.touchportal.annotations.Plugin import com.christophecvb.touchportal.helpers.PluginHelper import com.christophecvb.touchportal.TouchPortalPlugin -import com.christophecvb.touchportal.annotations.Data +import com.christophecvb.touchportal.annotations.* import com.christophecvb.touchportal.model.* import com.google.gson.JsonObject import java.util.logging.Level import java.util.logging.Logger import kotlin.system.exitProcess -@Suppress("unused") -@Plugin(version = BuildConfig.VERSION_CODE, colorDark = "#556677", colorLight = "#112233") +@Plugin( + name = BuildConfig.NAME, + version = BuildConfig.VERSION_CODE, + colorDark = "#556677", + colorLight = "#112233", + parentCategory = ParentCategory.MISC +) class TouchPortalSampleKotlinPlugin(parallelizeActions: Boolean) : TouchPortalPlugin(parallelizeActions), TouchPortalPlugin.TouchPortalPluginListener { companion object { /** - * Logger + * Logger used within the plugin */ private val LOGGER = Logger.getLogger(TouchPortalPlugin::class.java.name) @@ -51,7 +53,7 @@ class TouchPortalSampleKotlinPlugin(parallelizeActions: Boolean) : TouchPortalPl // Initiate the connection with the Touch Portal Plugin System val connectedPairedAndListening = touchPortalSampleKotlinPlugin.connectThenPairAndListen(touchPortalSampleKotlinPlugin) - @Suppress("ControlFlowWithEmptyBody") + if (connectedPairedAndListening) { // Let's go! LOGGER.log(Level.INFO, "Plugin with ID[${TouchPortalSampleKotlinPluginConstants.ID}] Connected and Paired!") @@ -61,7 +63,6 @@ class TouchPortalSampleKotlinPlugin(parallelizeActions: Boolean) : TouchPortalPl } } - @Suppress("unused") enum class Categories { @Category(imagePath = "images/icon-24.png") BaseCategory @@ -82,7 +83,7 @@ class TouchPortalSampleKotlinPlugin(parallelizeActions: Boolean) : TouchPortalPl } override fun onReceived(jsonMessage: JsonObject) {} - override fun onListChanged(tpListChangeMessage: TPListChangeMessage) {} + override fun onListChanged(tpListChangedMessage: TPListChangedMessage) {} override fun onInfo(tpInfoMessage: TPInfoMessage) {} override fun onBroadcast(tpBroadcastMessage: TPBroadcastMessage) {} override fun onSettings(tpSettingsMessage: TPSettingsMessage) {} diff --git a/build.gradle b/build.gradle index f66e5da4..8bff2046 100644 --- a/build.gradle +++ b/build.gradle @@ -4,8 +4,8 @@ allprojects { } } -ext.versionMajor = 8 -ext.versionMinor = 3 +ext.versionMajor = 9 +ext.versionMinor = 0 ext.versionPatch = 0 ext.isRelease = System.getenv('IS_RELEASE') == 'YES' diff --git a/settings.gradle b/settings.gradle index 2fcdf047..37ba134b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,6 +6,19 @@ pluginManagement { } } +dependencyResolutionManagement { + versionCatalogs { + libs { + library('gson', 'com.google.code.gson:gson:2.9.0') + library('okhttp', 'com.squareup.okhttp3:okhttp:4.9.3') + library('autoservice', 'com.google.auto.service:auto-service:1.0.1') + library('javapoet', 'com.squareup:javapoet:1.13.0') + library('junit', 'junit:junit:4.13.2') + library('kotlinstdlibjdk8', 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.10') + } + } +} + rootProject.name = 'TouchPortalPluginSDK' include 'Annotations' include 'Helpers'