Initial commit
This commit is contained in:
commit
0303da531e
12 changed files with 1004 additions and 0 deletions
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: Sauilitired
|
||||||
|
patreon: IntellectualSites # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
otechie: # Replace with a single Otechie username
|
||||||
|
custom: ['https://paypal.me/Sauilitired']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "maven" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
54
.github/workflows/codeql-analysis.yml
vendored
Normal file
54
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master, ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [master]
|
||||||
|
schedule:
|
||||||
|
- cron: '0 18 * * 3'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyse:
|
||||||
|
name: Analyse
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
# We must fetch at least the immediate parents so that if this is
|
||||||
|
# a pull request then we can checkout the head.
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
# If this run was triggered by a pull request event, then checkout
|
||||||
|
# the head of the pull request instead of the merge commit.
|
||||||
|
- run: git checkout HEAD^2
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
# Override language selection by uncommenting this and choosing your languages
|
||||||
|
with:
|
||||||
|
languages: java
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
24
.github/workflows/maven.yml
vendored
Normal file
24
.github/workflows/maven.yml
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# This workflow will build a Java project with Maven
|
||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||||
|
|
||||||
|
name: Java CI with Maven
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 1.8
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.8
|
||||||
|
- name: Build with Maven
|
||||||
|
run: mvn -B package --file pom.xml
|
||||||
213
.gitignore
vendored
Normal file
213
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
|
||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/jetbrains+all,maven,eclipse,java,git
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains+all,maven,eclipse,java,git
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.metadata
|
||||||
|
bin/
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
local.properties
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
.recommenders
|
||||||
|
|
||||||
|
# External tool builders
|
||||||
|
.externalToolBuilders/
|
||||||
|
|
||||||
|
# Locally stored "Eclipse launch configurations"
|
||||||
|
*.launch
|
||||||
|
|
||||||
|
# PyDev specific (Python IDE for Eclipse)
|
||||||
|
*.pydevproject
|
||||||
|
|
||||||
|
# CDT-specific (C/C++ Development Tooling)
|
||||||
|
.cproject
|
||||||
|
|
||||||
|
# CDT- autotools
|
||||||
|
.autotools
|
||||||
|
|
||||||
|
# Java annotation processor (APT)
|
||||||
|
.factorypath
|
||||||
|
|
||||||
|
# PDT-specific (PHP Development Tools)
|
||||||
|
.buildpath
|
||||||
|
|
||||||
|
# sbteclipse plugin
|
||||||
|
.target
|
||||||
|
|
||||||
|
# Tern plugin
|
||||||
|
.tern-project
|
||||||
|
|
||||||
|
# TeXlipse plugin
|
||||||
|
.texlipse
|
||||||
|
|
||||||
|
# STS (Spring Tool Suite)
|
||||||
|
.springBeans
|
||||||
|
|
||||||
|
# Code Recommenders
|
||||||
|
.recommenders/
|
||||||
|
|
||||||
|
# Annotation Processing
|
||||||
|
.apt_generated/
|
||||||
|
.apt_generated_test/
|
||||||
|
|
||||||
|
# Scala IDE specific (Scala & Java development for Eclipse)
|
||||||
|
.cache-main
|
||||||
|
.scala_dependencies
|
||||||
|
.worksheet
|
||||||
|
|
||||||
|
# Uncomment this line if you wish to ignore the project description file.
|
||||||
|
# Typically, this file would be tracked if it contains build/dependency configurations:
|
||||||
|
#.project
|
||||||
|
|
||||||
|
### Eclipse Patch ###
|
||||||
|
# Spring Boot Tooling
|
||||||
|
.sts4-cache/
|
||||||
|
|
||||||
|
### Git ###
|
||||||
|
# Created by git for backups. To disable backups in Git:
|
||||||
|
# $ git config --global mergetool.keepBackup false
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
# Created by git when using merge tools for conflicts
|
||||||
|
*.BACKUP.*
|
||||||
|
*.BASE.*
|
||||||
|
*.LOCAL.*
|
||||||
|
*.REMOTE.*
|
||||||
|
*_BACKUP_*.txt
|
||||||
|
*_BASE_*.txt
|
||||||
|
*_LOCAL_*.txt
|
||||||
|
*_REMOTE_*.txt
|
||||||
|
|
||||||
|
### Java ###
|
||||||
|
# Compiled class file
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
hs_err_pid*
|
||||||
|
|
||||||
|
### JetBrains+all ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### JetBrains+all Patch ###
|
||||||
|
# Ignores the whole .idea folder and all .iml files
|
||||||
|
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
modules.xml
|
||||||
|
.idea/misc.xml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
.idea/sonarlint
|
||||||
|
|
||||||
|
### Maven ###
|
||||||
|
target/
|
||||||
|
pom.xml.tag
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
pom.xml.next
|
||||||
|
release.properties
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
buildNumber.properties
|
||||||
|
.mvn/timing.properties
|
||||||
|
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
|
||||||
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/jetbrains+all,maven,eclipse,java,git
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 IntellectualSites
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
132
pom.xml
Normal file
132
pom.xml
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.intellectualsites</groupId>
|
||||||
|
<artifactId>Commands</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<inceptionYear>2020</inceptionYear>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>MIT License</name>
|
||||||
|
<url>https://raw.githubusercontent.com/Sauilitired/Commands/master/LICENSE</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
<name>Commands</name>
|
||||||
|
<description>Commands for Java</description>
|
||||||
|
<properties>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
<distributionManagement>
|
||||||
|
<repository>
|
||||||
|
<id>intellectualsites</id>
|
||||||
|
<name>IntellectualSites</name>
|
||||||
|
<url>https://mvn.intellectualsites.com/content/repositories/releases</url>
|
||||||
|
</repository>
|
||||||
|
<snapshotRepository>
|
||||||
|
<id>intellectualsites-snapshots</id>
|
||||||
|
<name>IntellectualSites Snapshots</name>
|
||||||
|
<url>https://mvn.intellectualsites.com/content/repositories/snapshots</url>
|
||||||
|
</snapshotRepository>
|
||||||
|
</distributionManagement>
|
||||||
|
<build>
|
||||||
|
<defaultGoal>clean package</defaultGoal>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
<version>2.8.2</version>
|
||||||
|
<configuration>
|
||||||
|
<skip>true</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.sonatype.plugins</groupId>
|
||||||
|
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||||
|
<version>1.6.8</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>default-deploy</id>
|
||||||
|
<phase>deploy</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>deploy</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<serverId>intellectualsites</serverId>
|
||||||
|
<nexusUrl>https://mvn.intellectualsites.com/</nexusUrl>
|
||||||
|
<skipStaging>true</skipStaging>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadocs</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>3.2.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.22.2</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>5.6.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.findbugs</groupId>
|
||||||
|
<artifactId>jsr305</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>29.0-jre</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
85
src/main/java/com/intellectualsites/commands/Command.java
Normal file
85
src/main/java/com/intellectualsites/commands/Command.java
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 IntellectualSites
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
package com.intellectualsites.commands;
|
||||||
|
|
||||||
|
import com.intellectualsites.commands.components.CommandComponent;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A command consists out of a chain of {@link com.intellectualsites.commands.components.CommandComponent command components}.
|
||||||
|
*/
|
||||||
|
public class Command {
|
||||||
|
|
||||||
|
private final CommandComponent<?>[] components;
|
||||||
|
|
||||||
|
private Command(@Nonnull final CommandComponent<?>[] commandComponents) {
|
||||||
|
this.components = Objects.requireNonNull(commandComponents, "Command components may not be null");
|
||||||
|
if (this.components.length == 0){
|
||||||
|
throw new IllegalArgumentException("At least one command component is required");
|
||||||
|
}
|
||||||
|
// Enforce ordering of command components
|
||||||
|
boolean foundOptional = false;
|
||||||
|
for (final CommandComponent<?> component : this.components) {
|
||||||
|
if (foundOptional && component.isRequired()) {
|
||||||
|
throw new IllegalArgumentException(String.format("Command component '%s' cannot be placed after an optional component", component.getName()));
|
||||||
|
} else if (!component.isRequired()) {
|
||||||
|
foundOptional = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a copy of the command component array
|
||||||
|
*
|
||||||
|
* @return Copy of the command component array
|
||||||
|
*/
|
||||||
|
@Nonnull public CommandComponent<?>[] getComponents() {
|
||||||
|
final CommandComponent<?>[] commandComponents = new CommandComponent<?>[this.components.length];
|
||||||
|
System.arraycopy(this.components, 0, commandComponents, 0, this.components.length);
|
||||||
|
return commandComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the longest chain of similar components for
|
||||||
|
* two commands
|
||||||
|
*
|
||||||
|
* @return List containing the longest shared component chain
|
||||||
|
*/
|
||||||
|
public List<CommandComponent<?>> getSharedComponentChain(@Nonnull final Command other) {
|
||||||
|
final List<CommandComponent<?>> commandComponents = new LinkedList<>();
|
||||||
|
for (int i = 0; i < this.components.length && i < other.components.length; i++) {
|
||||||
|
if (this.components[i].equals(other.components[i])) {
|
||||||
|
commandComponents.add(this.components[i]);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commandComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
155
src/main/java/com/intellectualsites/commands/CommandTree.java
Normal file
155
src/main/java/com/intellectualsites/commands/CommandTree.java
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
package com.intellectualsites.commands;
|
||||||
|
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.intellectualsites.commands.components.CommandComponent;
|
||||||
|
import com.intellectualsites.commands.components.StaticComponent;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tree containing all commands and command paths
|
||||||
|
*/
|
||||||
|
public class CommandTree {
|
||||||
|
|
||||||
|
private final Node<CommandComponent<?>> internalTree = new Node<>(null);
|
||||||
|
|
||||||
|
private CommandTree() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command tree instance
|
||||||
|
*
|
||||||
|
* @return New command tree
|
||||||
|
*/
|
||||||
|
@Nonnull public static CommandTree newTree() {
|
||||||
|
return new CommandTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a new command into the command tree
|
||||||
|
*
|
||||||
|
* @param command Command to insert
|
||||||
|
*/
|
||||||
|
public void insertCommand(@Nonnull final Command command) {
|
||||||
|
Node<CommandComponent<?>> node = this.internalTree;
|
||||||
|
for (final CommandComponent<?> component : command.getComponents()) {
|
||||||
|
Node<CommandComponent<?>> tempNode = node.getChild(component);
|
||||||
|
if (tempNode == null) {
|
||||||
|
tempNode = node.addChild(component);
|
||||||
|
}
|
||||||
|
node = tempNode;
|
||||||
|
}
|
||||||
|
if (node.getValue() != null) {
|
||||||
|
node.getValue().setOwningCommand(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go through all commands and register them, and verify the
|
||||||
|
* command tree contracts
|
||||||
|
*/
|
||||||
|
public void verifyAndRegister() {
|
||||||
|
// All top level commands are supposed to be registered in the command manager
|
||||||
|
this.internalTree.children.stream().map(Node::getValue).forEach(commandComponent -> {
|
||||||
|
if (!(commandComponent instanceof StaticComponent)) {
|
||||||
|
throw new IllegalStateException("Top level command component cannot be a variable");
|
||||||
|
}
|
||||||
|
// TODO: Register in the command handler
|
||||||
|
});
|
||||||
|
this.checkAmbiguity(this.internalTree);
|
||||||
|
// Verify that all leaf nodes have command registered
|
||||||
|
this.getLeaves(this.internalTree).forEach(leaf -> {
|
||||||
|
if (leaf.getOwningCommand() == null) {
|
||||||
|
// TODO: Custom exception type
|
||||||
|
throw new IllegalStateException("Leaf node does not have associated owning command");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAmbiguity(@Nonnull final Node<CommandComponent<?>> node) {
|
||||||
|
if (node.isLeaf()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int size = node.children.size();
|
||||||
|
for (final Node<CommandComponent<?>> child : node.children) {
|
||||||
|
if (child.getValue() != null && !child.getValue().isRequired() && size > 1) {
|
||||||
|
// TODO: Use a custom exception type here
|
||||||
|
throw new IllegalStateException("Ambiguous command node found: " + node.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.children.forEach(this::checkAmbiguity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CommandComponent<?>> getLeaves(@Nonnull final Node<CommandComponent<?>> node) {
|
||||||
|
final List<CommandComponent<?>> leaves = new LinkedList<>();
|
||||||
|
if (node.isLeaf()) {
|
||||||
|
if (node.getValue() != null) {
|
||||||
|
leaves.add(node.getValue());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node.children.forEach(child -> leaves.addAll(getLeaves(child)));
|
||||||
|
}
|
||||||
|
return leaves;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final class Node<T> {
|
||||||
|
|
||||||
|
private final List<Node<T>> children = new LinkedList<>();
|
||||||
|
private final T value;
|
||||||
|
|
||||||
|
private Node(@Nullable final T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Node<T>> getChildren() {
|
||||||
|
return Collections.unmodifiableList(this.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull private Node<T> addChild(@Nonnull final T child) {
|
||||||
|
final Node<T> node = new Node<>(child);
|
||||||
|
this.children.add(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable private Node<T> getChild(@Nonnull final T type) {
|
||||||
|
for (final Node<T> child : this.children) {
|
||||||
|
if (type.equals(child.getValue())) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLeaf() {
|
||||||
|
return this.children.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public T getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Node<?> node = (Node<?>) o;
|
||||||
|
return Objects.equal(getChildren(), node.getChildren()) &&
|
||||||
|
Objects.equal(getValue(), node.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(getChildren(), getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 IntellectualSites
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
package com.intellectualsites.commands.components;
|
||||||
|
|
||||||
|
import com.intellectualsites.commands.Command;
|
||||||
|
import com.intellectualsites.commands.parser.ComponentParser;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component that belongs to a command
|
||||||
|
*
|
||||||
|
* @param <T> The type that the component parses into
|
||||||
|
*/
|
||||||
|
public class CommandComponent<T> {
|
||||||
|
|
||||||
|
private static final Pattern NAME_PATTERN = Pattern.compile("[A-Za-z0-9]+");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the component is required
|
||||||
|
* or not. All components prior to any other required
|
||||||
|
* component must also be required, such that the predicate
|
||||||
|
* (∀ c_i ∈ required)({c_0, ..., c_i-1} ⊂ required) holds true,
|
||||||
|
* where {c_0, ..., c_n-1} is the set of command components.
|
||||||
|
*/
|
||||||
|
private final boolean required;
|
||||||
|
/**
|
||||||
|
* The command component name. This might be exposed
|
||||||
|
* to command senders and so should be choosen carefully.
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
/**
|
||||||
|
* The parser that is used to parse the command input
|
||||||
|
* into the corresponding command type
|
||||||
|
*/
|
||||||
|
private final ComponentParser<T> parser;
|
||||||
|
|
||||||
|
private Command owningCommand;
|
||||||
|
|
||||||
|
CommandComponent(final boolean required, @Nonnull final String name,
|
||||||
|
@Nonnull final ComponentParser<T> parser) {
|
||||||
|
this.required = required;
|
||||||
|
this.name = Objects.requireNonNull(name, "Name may not be null");
|
||||||
|
if (!NAME_PATTERN.asPredicate().test(name)) {
|
||||||
|
throw new IllegalArgumentException("Name must be alphanumeric");
|
||||||
|
}
|
||||||
|
this.parser = Objects.requireNonNull(parser, "Parser may not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command component
|
||||||
|
*
|
||||||
|
* @param clazz Argument class
|
||||||
|
* @param <T> Argument Type
|
||||||
|
* @return Component builder
|
||||||
|
*/
|
||||||
|
@Nonnull public static <T> CommandComponent.Builder<T> ofType(@Nonnull final Class<T> clazz) {
|
||||||
|
return new Builder<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether or not the command component is required
|
||||||
|
*
|
||||||
|
* @return {@code true} if the component is required, {@code false} if not
|
||||||
|
*/
|
||||||
|
public boolean isRequired() {
|
||||||
|
return this.required;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the command component name;
|
||||||
|
*
|
||||||
|
* @return Component name
|
||||||
|
*/
|
||||||
|
@Nonnull public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the parser that is used to parse the command input
|
||||||
|
* into the corresponding command type
|
||||||
|
*
|
||||||
|
* @return Command parser
|
||||||
|
*/
|
||||||
|
@Nonnull public ComponentParser<T> getParser() {
|
||||||
|
return this.parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull @Override public String toString() {
|
||||||
|
return String.format("CommandComponent{name=%s}", this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the owning command
|
||||||
|
*
|
||||||
|
* @return Owning command
|
||||||
|
*/
|
||||||
|
@Nullable public Command getOwningCommand() {
|
||||||
|
return this.owningCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the owning command
|
||||||
|
*
|
||||||
|
* @param owningCommand Owning command
|
||||||
|
*/
|
||||||
|
public void setOwningCommand(@Nonnull final Command owningCommand) {
|
||||||
|
if (this.owningCommand != null) {
|
||||||
|
throw new IllegalStateException("Cannot replace owning command");
|
||||||
|
}
|
||||||
|
this.owningCommand = owningCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final CommandComponent<?> that = (CommandComponent<?>) o;
|
||||||
|
return isRequired() == that.isRequired() && com.google.common.base.Objects.equal(getName(), that.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return com.google.common.base.Objects.hashCode(isRequired(), getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class Builder<T> {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private boolean required = true;
|
||||||
|
private ComponentParser<T> parser;
|
||||||
|
|
||||||
|
private Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the component name. Must be alphanumeric
|
||||||
|
*
|
||||||
|
* @param name Alphanumeric component name
|
||||||
|
* @return Builder instance
|
||||||
|
*/
|
||||||
|
@Nonnull public Builder<T> named(@Nonnull final String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the component is required.
|
||||||
|
* All components prior to any other required
|
||||||
|
* component must also be required, such that the predicate
|
||||||
|
* (∀ c_i ∈ required)({c_0, ..., c_i-1} ⊂ required) holds true,
|
||||||
|
* where {c_0, ..., c_n-1} is the set of command components.
|
||||||
|
*
|
||||||
|
* @return Builder instance
|
||||||
|
*/
|
||||||
|
@Nonnull public Builder<T> asRequired() {
|
||||||
|
this.required = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the component is optional.
|
||||||
|
* All components prior to any other required
|
||||||
|
* component must also be required, such that the predicate
|
||||||
|
* (∀ c_i ∈ required)({c_0, ..., c_i-1} ⊂ required) holds true,
|
||||||
|
* where {c_0, ..., c_n-1} is the set of command components.
|
||||||
|
*
|
||||||
|
* @return Builder instance
|
||||||
|
*/
|
||||||
|
@Nonnull public Builder<T> asOptional() {
|
||||||
|
this.required = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the component parser
|
||||||
|
*
|
||||||
|
* @param parser Component parser
|
||||||
|
* @return Builder instance
|
||||||
|
*/
|
||||||
|
@Nonnull public Builder<T> withParser(@Nonnull final ComponentParser<T> parser) {
|
||||||
|
this.parser = Objects.requireNonNull(parser, "Parser may not be null");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a command component from the builder settings
|
||||||
|
*
|
||||||
|
* @return Constructed component
|
||||||
|
*/
|
||||||
|
@Nonnull public CommandComponent<T> build() {
|
||||||
|
return new CommandComponent<>(this.required, this.name, this.parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 IntellectualSites
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
package com.intellectualsites.commands.components;
|
||||||
|
|
||||||
|
import com.intellectualsites.commands.parser.ComponentParser;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public final class StaticComponent extends CommandComponent<String> {
|
||||||
|
|
||||||
|
private StaticComponent(final boolean required, @Nonnull final String name, @Nonnull final String ... aliases) {
|
||||||
|
super(required, name, new StaticComponentParser(name, aliases));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final class StaticComponentParser implements ComponentParser<String> {
|
||||||
|
|
||||||
|
private StaticComponentParser(@Nonnull final String name, @Nonnull final String ... aliases) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 IntellectualSites
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
package com.intellectualsites.commands.parser;
|
||||||
|
|
||||||
|
public interface ComponentParser<T> {
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue